[
  {
    "path": ".dockerignore",
    "content": ".gitattributes\n.gitignore\n.github\n.git\ndocs\nDockerfile\n*.md\ndocker-compose.yml\n**/venv\n**/env\nlocal/bin\n*.pyc\n*.swp\n.pre-commit-config.yaml\n.dockerignore\ndpkg.log\ntemplates.dat\ntest\nexamples/install_scripts\nexamples/reports\nexamples/soap\nexamples/ejbca\nexamples/trigger\nsonar-project.properties\ncn_dump_hooks.py\nexception_test_hooks.py\ntests.py\nacme_srv.db.example\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n# all python files should be lf\n*.py text eol=lf\n"
  },
  {
    "path": ".github/.codecov.yml",
    "content": "ignore:\n  - \"examples/ca_handler/skeleton_ca_handler.py\"\n  - \"examples/ca_handler/certsrv.py\"\n  - \"examples/ca_handler/ms_wcce\"\n  - \"examples/eab_handler/skeleton_eab_handler.py\"\n  - \"examples/hooks\"\n  - \"docs/__init__.py\"\n  - \"acme_srv/monkey_patches.py\"\n  - \"acme_srv/threadwithreturnvalue.py\"\n  - 'tools/mswcce_connection_test.py'\n  - \"setup.py\"\n  - \"test\"\n"
  },
  {
    "path": ".github/Caddyfile",
    "content": "{\n\temail grindsa@foo.local\n\tacme_ca https://acme-srv.acme/acme/directory\n\tacme_ca_root /tmp/acme2certifier_cabundle.pem\n\tdebug\n}\n\ncaddy.acme {\n\troot * /usr/share/caddy\n\tfile_server browse\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: grindsa\ncustom: https://www.paypal.me/Gindsa\npatreon: GrindSa\nopen_collective: acme2certifier\n"
  },
  {
    "path": ".github/a2c.psql",
    "content": "DROP DATABASE IF EXISTS acme2certifier;\nCREATE DATABASE acme2certifier;\nCREATE USER acme2certifier WITH PASSWORD '1mmSvDFl';\nALTER ROLE acme2certifier SET client_encoding TO 'utf8';\nALTER ROLE acme2certifier SET default_transaction_isolation TO 'read committed';\nALTER ROLE acme2certifier SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE acme2certifier TO acme2certifier;\nGRANT ALL ON schema public TO acme2certifier;\nGRANT USAGE ON schema public TO acme2certifier;\nGRANT postgres TO acme2certifier;\n"
  },
  {
    "path": ".github/actions/acme_clients/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  REVOCATION:\n    description: \"Revocation method\"\n    required: true\n    default: \"true\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  VERIFY_CERT:\n    description: \"Verify certificate\"\n    required: true\n    default: \"true\"\n  USE_CERTBOT:\n    description: \"Use certbot\"\n    required: true\n    default: \"true\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n  TEST_ADL:\n    description: \"Test allowed_domainlist feature\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acme-sh/\n        sudo mkdir -p certbot/\n        sudo mkdir -p lego/ca\n        sudo cp .github/acme2certifier_cabundle.pem certbot/\n        sudo cp .github/acme2certifier_cabundle.pem lego/\n        if [ -f cert-2.pem ]; then\n          echo \"delete cert-2.pem\"\n          rm -f cert-2.pem\n        fi\n        if [ -f cert-1.pem ]; then\n          echo \"delete cert-1.pem\"\n          rm -f cert-1.pem\n        fi\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll lego\"\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke lego\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"#### HTTP - Revoke lego\"\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE revoke\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll acme.sh\"\n      run: |\n        echo \"##### HTTPS - Enroll acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --debug 1 --output-insecure --insecure\n          ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --keylength 2048 --debug 1 --output-insecure --insecure\n        fi\n\n        awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/ca.cer\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            echo \"Multiple CA certs\"\n            openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          else\n            echo \"Single Root ca\"\n            openssl verify -CAfile cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Renew acme.sh\"\n      if: ${{ inputs.RENEWAL == 'true' }}\n      run: |\n        echo \"##### HTTPS - Renew acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n           ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n        fi\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --renew --server https://$ACME_SERVER:$HTTPS_PORT  --force --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --debug 1 --output-insecure --insecure\n        awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/ca.cer\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            echo \"Multiple CA certs\"\n            openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          else\n            echo \"Single Root ca\"\n            openssl verify -CAfile cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke HTTP-01 single domain acme.sh\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"##### HTTPS - Revoke HTTP-01 single domain acme.sh #####\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --revoke --server https://$ACME_SERVER:$HTTPS_PORT --revoke -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --debug 2 --output-insecure  --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Decativate acme.sh #####\"\n      run: |\n        echo \"##### HTTPS - Decativate acme.sh\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --deactivate-account --server https://$ACME_SERVER:$HTTPS_PORT --debug 2 --output-insecure --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Enroll acme.sh\"\n      run: |\n        echo \"##### HTTP - Enroll acme.sh #####\"\n        sudo rm -rf acme-sh/*\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server http://$ACME_SERVER:$HTTP_PORT  --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --debug 1 --output-insecure --insecure\n          ECC=\"_ecc\"\n         else\n          echo \"use RSA\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server http://$ACME_SERVER:$HTTP_PORT  --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --keylength 2048 --debug 1 --output-insecure --insecure\n        fi\n        awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/ca.cer\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          else\n            echo \"single root ca\"\n            openssl verify -CAfile cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Renew acme.sh\"\n      if: ${{ inputs.RENEWAL == 'true' }}\n      run: |\n        echo \"##### HTTP - Renew acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n           ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n        fi\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --renew --server http://$ACME_SERVER:$HTTP_PORT  --force --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --debug 1 --output-insecure --insecure\n        awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/ca.cer\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          else\n            echo \"single root ca\"\n            openssl verify -CAfile cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Revoke HTTP-01 single domain acme.sh\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"##### HTTP - Revoke HTTP-01 single domain acme.sh #####\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --revoke --server http://$ACME_SERVER:$HTTP_PORT --revoke -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --debug 2 --output-insecure  --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Decativate acme.sh\"\n      run: |\n        echo \"##### HTTP - Decativate acme.sh #####\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --deactivate-account --server http://$ACME_SERVER:$HTTP_PORT --debug 2 --output-insecure --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll certbot\"\n      if: ${{ inputs.USE_CERTBOT == 'true' }}\n      run: |\n        echo \"##### HTTPS - Enroll certbot #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        fi\n\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n          else\n            echo \"single root ca\"\n            sudo openssl verify -CAfile cert-1.pem certbot/live/certbot/cert.pem\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke certbot\"\n      if: ${{ (inputs.USE_CERTBOT == 'true') && (inputs.REVOCATION == 'true') }}\n      run: |\n        echo \"##### HTTPS - Revoke certbot #####\"\n        docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --server https://$ACME_SERVER:$HTTPS_PORT --no-verify-ssl --delete-after-revoke --cert-name certbot\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Enroll certbot #####\"\n      if: ${{ inputs.USE_CERTBOT == 'true' }}\n      run: |\n        echo \"##### HTTP - Enroll certbot #####\"\n        sudo rm -rf certbot/*\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://$ACME_SERVER:$HTTP_PORT --standalone --preferred-challenges http --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://$ACME_SERVER:$HTTP_PORT --standalone --preferred-challenges http --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        fi\n\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n          else\n            echo \"single root ca\"\n            sudo openssl verify -CAfile cert-1.pem certbot/live/certbot/cert.pem\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Revoke certbot\"\n      if: ${{ (inputs.USE_CERTBOT == 'true') && (inputs.REVOCATION == 'true') }}\n      run: |\n        echo \"##### HTTP - Revoke certbot #####\"\n        docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --server http://$ACME_SERVER:$HTTP_PORT --delete-after-revoke --cert-name certbot\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll lego\"\n      run: |\n        echo \"##### HTTPS - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n         else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        fi\n\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego$HOSTNAME_SUFFIX.$NAME_SPACE.crt\n          else\n            echo \"single root ca\"\n            sudo openssl verify -CAfile cert-1.pem lego/certificates/lego$HOSTNAME_SUFFIX.$NAME_SPACE.crt\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke lego\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"##### HTTPS - Revoke lego #####\"\n        docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify  -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE revoke\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n      continue-on-error: true\n      id: legofail01\n      if: ${{ inputs.TEST_ADL == 'true' }}\n      run: |\n        echo \"##### HTTP - Enroll lego to test allowed domainlist feature #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER --tls-skip-verify  -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER --tls-skip-verify  -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Allowed domainlist feature - check  result \"\n      if: ${{ (inputs.TEST_ADL == 'true') && steps.legofail01.outcome != 'failure' }}\n      run: |\n        echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Delete acme-sh, letsencypt and lego folders\"\n      run: |\n        sudo rm -rf  lego/*\n        sudo rm -rf  acme-sh/*\n        sudo rm -rf  certbot/*\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/acmeshell/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  ALMA_START:\n    description: \"Start alma container\"\n    required: true\n    default: \"false\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n  IDEMPOTENT_FINALIZE:\n    description: \"Enable idempotent finalize testing\"\n    required: true\n    default: \"false\"\n\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acmeshell/\n      shell: bash\n\n    - name: \"Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Install acmeshell\"\n      if: ${{ inputs.ALMA_START == 'true' }}\n      run: |\n        wget -c https://github.com/cpu/acmeshell/releases/download/v0.0.2-rc4/acmeshell_0.0.2-rc4_Linux_x86_64.tar.gz -O - | tar -xz\n        mv acmeshell_0.0.2-rc4_Linux_x86_64/acmeshell acmeshell/\n        chmod +x acmeshell/acmeshell\n        ls -la acmeshell/\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Prepare shellfile including multiple finalize commands\"\n      if: ${{ inputs.ALMA_START == 'true' }}\n      working-directory: acmeshell\n      run: |\n        echo \"newAccount -contacts=foo@bar.local\" > commands.shell\n        echo \"newOrder -identifiers=acmeshell.acme\" >> commands.shell\n        echo \"getOrder -order 0\" >> commands.shell\n        echo \"getAuthz -order=0 -identifier=acmeshell.acme\" >> commands.shell\n        echo \"getChall -order=0 -identifier=acmeshell.acme -type=http-01\" >> commands.shell\n        echo \"solve -order=0 -identifier=acmeshell.acme -challengeType=http-01\" >> commands.shell\n        echo \"finalize -order=0\" >> commands.shell\n        echo \"finalize -order=0\" >> commands.shell\n      shell: bash\n\n    - name: \"Run alma container\"\n      if: ${{ inputs.ALMA_START == 'true' }}\n      run: |\n        docker run -id --name alma --network $NAME_SPACE -v $(pwd)/acmeshell:/acmeshell almalinux/9-minimal\n        sleep 5\n        docker ps\n        docker exec alma ls -la /acmeshell\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Run acmeshell enroll\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        ls -la .\n        docker exec alma bash -c 'curl -f http://acme-srv/directory'\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell enroll log without idempotent finalize option\"\n      working-directory: acmeshell\n      id: acmeshell01\n      continue-on-error: true\n      run: |\n        ls -la .\n        grep \"urn:ietf:params:acme:error:orderNotReady\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check  result \"\n      if:  ${{ (inputs.IDEMPOTENT_FINALIZE == 'false') && (steps.acmeshell01.outcome != 'success')}}\n      run: |\n        echo \"legofail outcome is ${{steps.acmeshell01.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Check  result \"\n      if:  ${{ (inputs.IDEMPOTENT_FINALIZE == 'true') && (steps.acmeshell01.outcome != 'failure')}}\n      run: |\n        echo \"legofail outcome is ${{steps.acmeshell01.outcome }}\"\n        exit 1\n      shell: bash\n"
  },
  {
    "path": ".github/actions/cert_gen/action.yml",
    "content": "name: \"cert_gen\"\ndescription: \"Generate Certificates\"\ninputs:\n  ISSUING_CA_KEY:\n    description: \"Path to the Issuing-CA private key\"\n    required: true\n    default: \"test/ca/sub-ca-key.pem\"\n  ISSUING_CA_CERT:\n    description: \"Path to the CA certificate\"\n    required: true\n    default: \"test/ca/sub-ca-cert.pem\"\n  ISSUING_CA_PASSPHRASE:\n    description: \"Passphrase for the private key\"\n    required: true\n    default: \"Test1234\"\n  ROOT_CA_CERT:\n    description: \"Path to the root CA certificate\"\n    required: true\n    default: \"test/ca/root-ca-cert.pem\"\n  DESTINATION_PATH:\n    description: \"Path for key and certificates\"\n    required: false\n    default: \".github\"\n  EE_KEY:\n    description: \"Path to the end-entity private key\"\n    required: true\n    default: \"acme2certifier_key.pem\"\n  EE_CERT:\n    description: \"Path to the end-entity certificate\"\n    required: true\n    default: \"acme2certifier_cert.pem\"\n  EE_CSR:\n    description: \"Path to the end-entity certificate signing request\"\n    required: true\n    default: \"acme2certifier_csr.pem\"\n  EE_BUNDLE:\n    description: \"Path to the end-entity certificate bundle\"\n    required: true\n    default: \"acme2certifier.pem\"\n  CA_BUNDLE:\n    description: \"Path to the CA bundle\"\n    required: true\n    default: \"acme2certifier_cabundle.pem\"\n  OS:\n    description: \"Operating System\"\n    required: true\n    default: \"Linux\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"generate keys and certificates\"\n      if: ${{ inputs.OS == 'Linux' }}\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n        openssl req -nodes -newkey rsa:2048 -keyout $DESTINATION_PATH/$EE_KEY -out $DESTINATION_PATH/$EE_CSR -batch -subj \"/CN=acme_srv\" -addext \"subjectAltName=DNS:acme_srv,DNS:acme_srv.acme,DNS:localhost,DNS:acme-srv,DNS:acme-srv.acme\" -addext \"keyUsage = digitalSignature, keyEncipherment, dataEncipherment\" -addext \"extendedKeyUsage = serverAuth\" -addext \"basicConstraints=CA:false\"\n        openssl x509 -req -in $DESTINATION_PATH/$EE_CSR -CA $ISSUING_CA_CERT -CAkey $ISSUING_CA_KEY -CAcreateserial -out $DESTINATION_PATH/$EE_CERT -copy_extensions copy -days 30 -sha256 --passin pass:$ISSUING_CA_PASSPHRASE\n        cp $DESTINATION_PATH/$EE_KEY $DESTINATION_PATH/$EE_BUNDLE\n        cat $DESTINATION_PATH/$EE_CERT >> $DESTINATION_PATH/$EE_BUNDLE\n        cat test/ca/sub-ca-cert.pem >> $DESTINATION_PATH/$EE_BUNDLE\n        cat test/ca/root-ca-cert.pem >> $DESTINATION_PATH/$EE_BUNDLE\n        cp test/ca/sub-ca-cert.pem $DESTINATION_PATH/$CA_BUNDLE\n        cat test/ca/root-ca-cert.pem >> $DESTINATION_PATH/$CA_BUNDLE\n      shell: bash\n      env:\n        ISSUING_CA_KEY: ${{ inputs.ISSUING_CA_KEY }}\n        ISSUING_CA_CERT: ${{ inputs.ISSUING_CA_CERT }}\n        ROOT_CA_CERT: ${{ inputs.ROOT_CA_CERT }}\n        ISSUING_CA_PASSPHRASE: ${{ inputs.ISSUING_CA_PASSPHRASE }}\n        DESTINATION_PATH: ${{ inputs.DESTINATION_PATH }}\n        EE_KEY: ${{ inputs.EE_KEY }}\n        EE_CERT: ${{ inputs.EE_CERT }}\n        EE_CSR: ${{ inputs.EE_CSR }}\n        EE_BUNDLE: ${{ inputs.EE_BUNDLE }}\n        CA_BUNDLE: ${{ inputs.CA_BUNDLE }}\n\n    - name: \"generate keys and certificates\"\n      if: ${{ inputs.OS == 'Windows' }}\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n        openssl req -nodes -newkey rsa:2048 -keyout $DESTINATION_PATH/$EE_KEY -out $DESTINATION_PATH/$EE_CSR -batch -subj \"//CN=acme_srv\" -addext \"subjectAltName=DNS:acme_srv,DNS:acme_srv.acme,DNS:localhost,DNS:acme-srv,DNS:acme-srv.acme\" -addext \"keyUsage = digitalSignature, keyEncipherment, dataEncipherment\" -addext \"extendedKeyUsage = serverAuth\" -addext \"basicConstraints=CA:false\"\n        openssl x509 -req -in $DESTINATION_PATH/$EE_CSR -CA $ISSUING_CA_CERT -CAkey $ISSUING_CA_KEY -CAcreateserial -out $DESTINATION_PATH/$EE_CERT -copy_extensions copy -days 30 -sha256 --passin pass:$ISSUING_CA_PASSPHRASE\n        cp $DESTINATION_PATH/$EE_KEY $DESTINATION_PATH/$EE_BUNDLE\n        cat $DESTINATION_PATH/$EE_CERT >> $DESTINATION_PATH/$EE_BUNDLE\n        cat test/ca/sub-ca-cert.pem >> $DESTINATION_PATH/$EE_BUNDLE\n        cat test/ca/root-ca-cert.pem >> $DESTINATION_PATH/$EE_BUNDLE\n        cp test/ca/sub-ca-cert.pem $DESTINATION_PATH/$CA_BUNDLE\n        cat test/ca/root-ca-cert.pem >> $DESTINATION_PATH/$CA_BUNDLE\n      shell: bash\n      env:\n        ISSUING_CA_KEY: ${{ inputs.ISSUING_CA_KEY }}\n        ISSUING_CA_CERT: ${{ inputs.ISSUING_CA_CERT }}\n        ROOT_CA_CERT: ${{ inputs.ROOT_CA_CERT }}\n        ISSUING_CA_PASSPHRASE: ${{ inputs.ISSUING_CA_PASSPHRASE }}\n        DESTINATION_PATH: ${{ inputs.DESTINATION_PATH }}\n        EE_KEY: ${{ inputs.EE_KEY }}\n        EE_CERT: ${{ inputs.EE_CERT }}\n        EE_CSR: ${{ inputs.EE_CSR }}\n        EE_BUNDLE: ${{ inputs.EE_BUNDLE }}\n        CA_BUNDLE: ${{ inputs.CA_BUNDLE }}\n\n    - name: \"Generate Django secret key and update settings files\"\n      run: |\n        # Generate a new Django secret key\n        DJANGO_SECRET=$(python3 -c \"import secrets; print(secrets.token_urlsafe(50))\")\n        echo \"Generated Django secret key\"\n\n        # Add the secret key to all three Django settings files\n        echo \"\" >> .github/django_settings.py\n        echo \"# SECURITY WARNING: keep the secret key used in production secret!\" >> .github/django_settings.py\n        echo \"SECRET_KEY = \\\"$DJANGO_SECRET\\\"\" >> .github/django_settings.py\n\n        echo \"\" >> .github/django_settings_mariadb.py\n        echo \"# SECURITY WARNING: keep the secret key used in production secret!\" >> .github/django_settings_mariadb.py\n        echo \"SECRET_KEY = \\\"$DJANGO_SECRET\\\"\" >> .github/django_settings_mariadb.py\n        sed -i \"s/\\\"XXX\\\": \\\"XXX\\\"/\\\"PASSWORD\\\": \\\"1mmSvDFl\\\"/g\" .github/django_settings_mariadb.py\n\n        echo \"\" >> .github/django_settings_psql.py\n        echo \"# SECURITY WARNING: keep the secret key used in production secret!\" >> .github/django_settings_psql.py\n        echo \"SECRET_KEY = \\\"$DJANGO_SECRET\\\"\" >> .github/django_settings_psql.py\n        sed -i \"s/\\\"XXX\\\": \\\"XXX\\\"/\\\"PASSWORD\\\": \\\"1mmSvDFl\\\"/g\" .github/django_settings_psql.py\n\n        echo \"\" >> .github/django_settings_mssql.py\n        echo \"# SECURITY WARNING: keep the secret key used in production secret!\" >> .github/django_settings_mssql.py\n        echo \"SECRET_KEY = \\\"$DJANGO_SECRET\\\"\" >> .github/django_settings_mssql.py\n        sed -i \"s/\\\"XXX\\\": \\\"XXX\\\"/\\\"PASSWORD\\\": \\\"1mmSvDFl\\\"/g\" .github/django_settings_mssql.py\n\n        echo \"Django secret key added to all settings files\"\n      shell: bash\n"
  },
  {
    "path": ".github/actions/container_build/action.yml",
    "content": "name: \"container_build\"\ndescription: \"Build Container\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker/\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Build docker compose (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n        echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n        sudo apt update\n        sudo apt install -y docker-compose-plugin\n        sed -i \"s/wsgi/$DB_HANDLER/g\" .env\n        sed -i \"s/apache2/$WEB_SRV/g\" .env\n        # cat .env\n        docker compose build\n      shell: bash\n      env:\n        WEB_SRV: ${{ inputs.WEB_SRV }}\n        DB_HANDLER: ${{ inputs.DB_HANDLER }}\n"
  },
  {
    "path": ".github/actions/container_build_upload/action.yml",
    "content": "name: \"container_build_upload\"\ndescription: \"Build and Upload Container\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Build container\"\n    uses: ./.github/actions/container_build\n    with:\n      DB_HANDLER: ${{ inputs.DB_HANDLER }}\n      WEB_SRV: ${{ inputs.WEB_SRV }}\n\n  - name: \"Save container\"\n    run: |\n      docker images\n      mkdir -p /tmp/a2c\n      docker save acme2certifier/$DB_HANDLER > /tmp/a2c/a2c-${{ github.run_id }}.$WEB_SRV.$DB_HANDLER.tar\n      gzip /tmp/a2c/a2c-${{ github.run_id }}.$WEB_SRV.$DB_HANDLER.tar\n    shell: bash\n    env:\n      DB_HANDLER: ${{ inputs.DB_HANDLER }}\n      WEB_SRV: ${{ inputs.WEB_SRV }}\n\n  - name: \"Upload container package\"\n    uses: actions/upload-artifact@v7\n    with:\n      name: a2c-${{ github.run_id }}.${{ inputs.WEB_SRV }}.${{ inputs.DB_HANDLER }}.tar.gz\n      path: /tmp/a2c\n"
  },
  {
    "path": ".github/actions/container_check/action.yml",
    "content": "name: \"container_check\"\ndescription: \"Check container configuration\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker/\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Logs\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n          docker compose logs | grep -i $WEB_SRV\n          if [ \"$DB_HANDLER\" == \"django\" ]; then\n              docker compose logs | grep -i migrations\n          else\n            docker compose logs | grep -i $DB_HANDLER\n          fi\n      env:\n        WEB_SRV: ${{ inputs.WEB_SRV }}\n        DB_HANDLER: ${{ inputs.DB_HANDLER }}\n      shell: bash\n"
  },
  {
    "path": ".github/actions/container_down/action.yml",
    "content": "name: \"container_down\"\ndescription: \"Stop a2c container\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker/\"\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Stop a2c instance (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n          # sed -i \"s/name: acme/name: $NAME_SPACE/g\" docker-compose.yml\n          docker compose down\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      shell: bash\n"
  },
  {
    "path": ".github/actions/container_load/action.yml",
    "content": "name: \"container_load\"\ndescription: \"Download and import container image\"\ninputs:\n  RUN_ID:\n    description: \"The run ID of the workflow run that produced the artifact\"\n    required: true\n  ARTIFACT_NAME:\n    description: \"The name of the artifact to download\"\n    required: true\n  DESTINATION_PATH:\n    description: \"The path to download the artifact to\"\n    required: false\n    default: \"./\"\n  TOKEN:\n    description: \"GitHub token with permissions to access the artifact\"\n    required: false\n    default: \"\"\n  REPO:\n    description: \"The repository in the format owner/repo. Defaults to the current repository.\"\n    required: false\n    default: \"\"\n  OS:\n    description: \"Operating System\"\n    required: true\n    default: \"Linux\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Download container\"\n      uses: ./.github/actions/download_artifact\n      with:\n        RUN_ID: ${{ inputs.RUN_ID }}\n        ARTIFACT_NAME: ${{ inputs.ARTIFACT_NAME }}.gz\n        DESTINATION_PATH: ${{ inputs.DESTINATION_PATH }}\n        TOKEN: ${{ inputs.TOKEN }}\n        REPO: ${{ inputs.REPO }}\n\n    - name: \"Import container\"\n      run: |\n        cd $DESTINATION_PATH\n        gunzip -f $ARTIFACT_NAME.gz\n        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n        echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n        sudo apt update\n        sudo apt install -y docker-compose-plugin\n        docker load -i $ARTIFACT_NAME\n        docker images\n      shell: bash\n      env:\n        DESTINATION_PATH: ${{ inputs.DESTINATION_PATH }}\n        ARTIFACT_NAME: ${{ inputs.ARTIFACT_NAME }}\n"
  },
  {
    "path": ".github/actions/container_prep/action.yml",
    "content": "name: \"container_prep\"\ndescription: \"Prepare environment for container installation\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DJANGO_DB:\n    description: \"Django database\"\n    required: false\n  CONTAINER_BUILD:\n    description: \"Build container\"\n    required: true\n    default: \"true\"\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  IPV6:\n    description: \"IPv6\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Generate keys and certificates\"\n      uses: ./.github/actions/cert_gen\n\n    - name: \"Setup environment\"\n      run: |\n        echo \"IPv6 is $IPV6\"\n        if [ \"$IPV6\" == \"false\" ]; then\n          echo \"create v4 namespace\"\n          docker network create $NAME_SPACE\n        else\n          echo \"create v6 namespace\"\n          docker network create $NAME_SPACE --ipv6 --subnet \"fdbb:6445:65b4:0a60::/64\"\n        fi\n        sudo mkdir -p examples/Docker/data\n        sudo cp .github/acme2certifier.pem examples/Docker/data/acme2certifier.pem\n        sudo cp .github/acme2certifier_cert.pem examples/Docker/data/acme2certifier_cert.pem\n        sudo cp .github/acme2certifier_key.pem examples/Docker/data/acme2certifier_key.pem\n        if [ -z \"$DJANGO_DB\" ]; then\n            sudo cp .github/django_settings.py examples/Docker/data/settings.py\n        else\n            if [ \"$DJANGO_DB\" != \"sqlite3\" ]; then\n                echo \"Using $DJANGO_DB as django database\"\n                sudo cp .github/django_settings_$DJANGO_DB.py examples/Docker/data/settings.py\n            else\n                echo \"Using sqlite3 as django database\"\n                sudo cp .github/django_settings.py examples/Docker/data/settings.py\n            fi\n        fi\n      env:\n        DJANGO_DB: ${{ inputs.DJANGO_DB }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        IPV6: ${{ inputs.IPV6 }}\n      shell: bash\n\n    - name: \"Build docker compose (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      if: inputs.CONTAINER_BUILD == 'true'\n      uses: ./.github/actions/container_build\n      with:\n        WEB_SRV: ${{ inputs.WEB_SRV }}\n        DB_HANDLER: ${{ inputs.DB_HANDLER }}\n\n    - name: \"Prepare container environment file (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      if: inputs.CONTAINER_BUILD != 'true'\n      working-directory: examples/Docker/\n      run: |\n        sed -i \"s/wsgi/$DB_HANDLER/g\" .env\n        sed -i \"s/apache2/$WEB_SRV/g\" .env\n      env:\n        WEB_SRV: ${{ inputs.WEB_SRV }}\n        DB_HANDLER: ${{ inputs.DB_HANDLER }}\n      shell: bash\n\n    - name: \"Spin-up a2c instance (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      if: inputs.CONTAINER_BUILD == 'true'\n      uses: ./.github/actions/container_up\n      with:\n        WEB_SRV: ${{ inputs.WEB_SRV }}\n        DB_HANDLER: ${{ inputs.DB_HANDLER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Instanciate Mariadb\"\n      if: inputs.DJANGO_DB == 'mariadb'\n      uses: ./.github/actions/mariadb_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Instanciate Postgres\"\n      if: inputs.DJANGO_DB == 'psql'\n      uses: ./.github/actions/psql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Instanciate MSSQL\"\n      if: inputs.DJANGO_DB == 'mssql'\n      uses: ./.github/actions/mssql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/container_run/action.yml",
    "content": "name: \"container_up\"\ndescription: \"instanciate a2c container\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker\"\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  VERSION:\n    description: \"a2c version\"\n    required: false\n    default: \"latest\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Spin-up a2c instance (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n        docker run -d -p 80:80 -p 443:443 --rm -id --network $NAME_SPACE --name=acme-srv -v \"$(pwd)/data\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:$VERSION-$WEBSRV-$DBHANDLER\n        docker logs acme-srv\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        WORKING_DIRECTORY: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n        WEBSRV: ${{ inputs.WEB_SRV }}\n        DBHANDLER: ${{ inputs.DB_HANDLER }}\n        VERSION: ${{ inputs.VERSION }}\n      shell: bash\n"
  },
  {
    "path": ".github/actions/container_up/action.yml",
    "content": "name: \"container_up\"\ndescription: \"instanciate a2c container\"\ninputs:\n  DB_HANDLER:\n    description: \"Database handler\"\n    required: true\n    default: \"wsgi\"\n  WEB_SRV:\n    description: \"Web server\"\n    required: true\n    default: \"apache2\"\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker/\"\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Spin-up a2c instance (${{ inputs.WEB_SRV }}_${{ inputs.DB_HANDLER }})\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n          sed -i \"s/name: acme/name: $NAME_SPACE/g\" docker-compose.yml\n          docker compose up -d --no-build\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      shell: bash\n"
  },
  {
    "path": ".github/actions/deb_build/action.yml",
    "content": "name: \"deb_build\"\ndescription: \"Build deb package\"\noutputs:\n  deb_file_name:\n    description: \"Name of the debian package file\"\n    value: acme2certifier_${{ env.TAG_NAME }}-1_all.deb\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: Retrieve Version from version.py\n      run: |\n        echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      shell: bash\n\n    - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n      shell: bash\n\n    - name: \"Install Firefox from Mozilla\"\n      run: |\n        sudo apt-get update\n        sudo install -d -m 0755 /etc/apt/keyrings\n        wget -q https://packages.mozilla.org/apt/repo-signing-key.gpg -O- | sudo tee /etc/apt/keyrings/packages.mozilla.org.asc > /dev/null\n        echo \"deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main\" | sudo tee -a /etc/apt/sources.list.d/mozilla.list > /dev/null\n        echo '\n        Package: *\n        Pin: origin packages.mozilla.org\n        Pin-Priority: 1000\n        ' | sudo tee /etc/apt/preferences.d/mozilla\n        sudo apt update && sudo apt install -y firefox --allow-downgrades\n      shell: bash\n\n    - name: \"Prepare environment to build deb package\"\n      run: |\n        sudo apt-get update && sudo apt-get -y upgrade\n        sudo apt-get -y install build-essential fakeroot dpkg-dev devscripts debhelper  --allow-downgrades\n        rm setup.py\n        rm -f examples/ngnix/acme2certifier.te\n        rm -f examples/nginx/supervisord.conf\n        rm -f examples/nginx/uwsgi.service\n        sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv.conf\n        sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv_ssl.conf\n        sed -i \"s/\\/run\\/uwsgi\\/acme.sock/acme.sock/g\" examples/nginx/acme2certifier.ini\n        sed -i \"s/nginx/www-data/g\" examples/nginx/acme2certifier.ini\n        echo \"plugins=python3\" >> examples/nginx/acme2certifier.ini\n        cat <<EOT > examples/nginx/acme2certifier.service\n        [Unit]\n        Description=uWSGI instance to serve acme2certifier\n        After=network.target\n\n        [Service]\n        User=www-data\n        Group=www-data\n        WorkingDirectory=/var/www/acme2certifier\n        Environment=\"PATH=/var/www/acme2certifier\"\n        ExecStart=uwsgi --ini /var/www/acme2certifier/acme2certifier.ini\n\n        [Install]\n        WantedBy=multi-user.target\n        EOT\n        cp -R examples/install_scripts/debian ./\n        sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" debian/changelog\n        cd ../\n        tar cvfz ../acme2certifier_${{ env.TAG_NAME }}.orig.tar.gz ./\n      shell: bash\n\n    - name: \"Build debian package\"\n      run: |\n        dpkg-buildpackage -uc -us\n        dpkg -c ../acme2certifier_${{ env.TAG_NAME }}-1_all.deb\n      shell: bash\n"
  },
  {
    "path": ".github/actions/deb_build_upload/action.yml",
    "content": "name: \"deb_build_upload\"\ndescription: \"Build and Upload package\"\ninputs:\n  NO_VERSION:\n    description: \"If true, do not append version to package\"\n    required: false\n    default: \"false\"\noutputs:\n  deb_file_name:\n    description: \"Name of the DEB package file\"\n    value: acme2certifier_${{ env.TAG_NAME }}-${{ github.run_id }}-1_all.deb\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Build deb package\"\n    id: deb_build\n    uses: ./.github/actions/deb_build\n\n  - name: \"Rename deb package\"\n    if: ${{ inputs.NO_VERSION != 'false' }}\n    run: |\n      sudo mv ../acme2certifier_${{ env.TAG_NAME }}-1_all.deb ./acme2certifier-${{ github.run_id }}-1_all.deb\n    shell: bash\n\n  - name: \"Upload deb package\"\n    if: ${{ inputs.NO_VERSION != 'false' }}\n    uses: actions/upload-artifact@v7\n    with:\n      name: acme2certifier-${{ github.run_id }}-1_all.deb\n      path: acme2certifier-${{ github.run_id }}-1_all.deb\n\n  - name: \"Rename deb package\"\n    if: ${{ inputs.NO_VERSION == 'false' }}\n    run: |\n      sudo mv ../acme2certifier_${{ env.TAG_NAME }}-1_all.deb ./acme2certifier_${{ env.TAG_NAME }}-${{ github.run_id }}-1_all.deb\n    shell: bash\n\n  - name: \"Upload deb package\"\n    if: ${{ inputs.NO_VERSION == 'false' }}\n    uses: actions/upload-artifact@v7\n    with:\n      name: acme2certifier_${{ env.TAG_NAME }}-${{ github.run_id }}-1_all.deb\n      path: acme2certifier_${{ env.TAG_NAME }}-${{ github.run_id }}-1_all.deb\n"
  },
  {
    "path": ".github/actions/deb_prep/action.yml",
    "content": "name: \"deb_prep\"\ndescription: \"Prepare environment for deb installation\"\ninputs:\n  GH_USER:\n    description: \"GIT user for SBOM repo\"\n    required: true\n  GH_SBOM_REPO_TOKEN:\n    description: \"GIT token for SBOM repo\"\n    required: true\n  DJANGO_DB:\n    description: \"Django database\"\n  DEB_BUILD:\n    description: \"Build DEB\"\n    required: true\n    default: \"true\"\n  NAME_SPACE:\n    description: \"Name space\"\n    required: true\n    default: \"acme\"\n  IPV6:\n    description: \"IPv6\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Build deb package\"\n      if: inputs.DEB_BUILD == 'true'\n      id: deb_build\n      uses: ./.github/actions/deb_build\n\n    - name: \"Generate keys and certificates\"\n      uses: ./.github/actions/cert_gen\n\n    - name: \"Setup environment for ubuntu installation\"\n      run: |\n        echo \"IPv6 is $IPV6\"\n        if [ \"$IPV6\" == \"false\" ]; then\n          echo \"create v4 namespace\"\n          docker network create $NAME_SPACE\n        else\n          echo \"create v6 namespace\"\n          docker network create $NAME_SPACE --ipv6 --subnet \"fdbb:6445:65b4:0a60::/64\"\n        fi\n        sudo mkdir -p data/volume/acme2certifier\n        sudo mkdir -p data/nginx\n        sudo chmod -R 777 data\n        sudo cp examples/Docker/ubuntu-systemd/deb_tester.sh data\n        sudo cp examples/Docker/ubuntu-systemd/django_tester.sh data\n        sudo cp .github/acme2certifier_cert.pem data/volume/acme2certifier_cert.pem\n        sudo cp .github/acme2certifier_key.pem data/volume/acme2certifier_key.pem\n        sudo cp .github/acme2certifier.pem data/volume/acme2certifier.pem\n\n        if [ -z \"$DJANGO_DB\" ] || [ \"$DJANGO_DB\" == \"sqlite3\" ]; then\n            echo \"Using default django settings for sqlite3\"\n            sudo cp .github/django_settings.py data/volume/acme2certifier/settings.py\n        else\n            sudo cp .github/django_settings_$DJANGO_DB.py data/volume/acme2certifier/settings.py\n        fi\n      env:\n        DJANGO_DB: ${{ inputs.DJANGO_DB }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        IPV6: ${{ inputs.IPV6 }}\n      shell: bash\n\n    - name: \"Generate keys and certificates\"\n      uses: ./.github/actions/cert_gen\n      with:\n        DESTINATION_PATH: \".github\"\n        EE_KEY: \"acme2certifier_key.pem\"\n        EE_CERT: \"acme2certifier_cert.pem\"\n        EE_CSR: \"acme2certifier_csr.pem\"\n        EE_BUNDLE: \"acme2certifier.pem\"\n        CA_BUNDLE: \"acme2certifier_cabundle.pem\"\n        ISSUING_CA_KEY: \"test/ca/sub-ca-key.pem\"\n        ISSUING_CA_CERT: \"test/ca/sub-ca-cert.pem\"\n        ISSUING_CA_PASSPHRASE: \"Test1234\"\n        ROOT_CA_CERT: \"test/ca/root-ca-cert.pem\"\n\n\n    - name: \"Instanciate Mariadb\"\n      if: inputs.DJANGO_DB == 'mariadb'\n      uses: ./.github/actions/mariadb_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Instanciate Postgres\"\n      if: inputs.DJANGO_DB == 'psql'\n      uses: ./.github/actions/psql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Prepare addditional environment for MSSQL\"\n      if: inputs.DJANGO_DB == 'mssql'\n      run: |\n        echo \"Download Microsoft repository configuration package\"\n        curl https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb --output data/packages-microsoft-prod.deb\n        ls -la data/\n      env:\n        DJANGO_DB: ${{ inputs.DJANGO_DB }}\n        VERSION: ${{ inputs.RH_VERSION }}\n      shell: bash\n\n    - name: \"Instanciate MSSQL\"\n      if: inputs.DJANGO_DB == 'mssql'\n      uses: ./.github/actions/mssql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Instanciate Ubuntu 24.04\"\n      run: |\n        docker run -d --name acme-srv --network $NAME_SPACE -p 22280:80 --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v \"$(pwd)/data\":/tmp/acme2certifier jrei/systemd-ubuntu:24.04\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/download_artifact/action.yml",
    "content": "name: \"download artifact\"\ndescription: \"download an artifact from a workflow run\"\ninputs:\n  RUN_ID:\n    description: \"The run ID of the workflow run that produced the artifact\"\n    required: true\n  ARTIFACT_NAME:\n    description: \"The name of the artifact to download\"\n    required: true\n  DESTINATION_PATH:\n    description: \"The path to download the artifact to\"\n    required: false\n    default: \"./\"\n  TOKEN:\n    description: \"GitHub token with permissions to access the artifact\"\n    required: false\n    default: \"\"\n  REPO:\n    description: \"The repository in the format owner/repo. Defaults to the current repository.\"\n    required: false\n    default: \"\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Get artifact\"\n      run: |\n        mkdir -p $DESTINATION_PATH && cd $DESTINATION_PATH\n        ART_ID=$(gh api repos/$REPO/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name==\"'$ARTIFACT_NAME'\") | .id')\n        if [ -z \"$ART_ID\" ]; then\n          echo \"Artifact $ARTIFACT_NAME not found in run $RUN_ID\" >&2\n          exit 1\n        fi\n\n        gh api repos/$REPO/actions/artifacts/$ART_ID/zip > \"$ARTIFACT_NAME.zip\"\n        unzip -o \"$ARTIFACT_NAME.zip\"\n        rm -f \"$ARTIFACT_NAME.zip\"\n\n      shell: bash\n      env:\n        DESTINATION_PATH: ${{ inputs.DESTINATION_PATH }}\n        GH_TOKEN: ${{ inputs.TOKEN }}\n        REPO: ${{ inputs.REPO }}\n        RUN_ID: ${{ inputs.RUN_ID }}\n        ARTIFACT_NAME: ${{ inputs.ARTIFACT_NAME }}\n"
  },
  {
    "path": ".github/actions/dump-secrets-to-json/action.yml",
    "content": "name: 'Dump Secrets to JSON'\ndescription: 'Dumps a list of secrets into a JSON structure with secret names as keys and content as values'\n\ninputs:\n  secret_names:\n    description: 'Comma-separated list of secret names to dump'\n    required: true\n  output_file:\n    description: 'Output file path for the JSON structure'\n    required: false\n    default: 'secrets.json'\n  mask_values:\n    description: 'Whether to mask secret values in logs (true/false)'\n    required: false\n    default: 'true'\n\noutputs:\n  json_file:\n    description: 'Path to the generated JSON file'\n    value: ${{ steps.create-json.outputs.json_file }}\n  secret_count:\n    description: 'Number of secrets processed'\n    value: ${{ steps.create-json.outputs.secret_count }}\n\nruns:\n  using: 'composite'\n  steps:\n    - name: Create secrets JSON\n      id: create-json\n      shell: bash\n      env:\n        SECRET_NAMES: ${{ inputs.secret_names }}\n        OUTPUT_FILE: ${{ inputs.output_file }}\n        MASK_VALUES: ${{ inputs.mask_values }}\n      run: |\n        # Initialize JSON object\n        echo \"{\" > \"$OUTPUT_FILE\"\n\n        # Convert comma-separated list to array\n        IFS=',' read -ra SECRETS <<< \"$SECRET_NAMES\"\n        secret_count=0\n        total_secrets=${#SECRETS[@]}\n\n        echo \"Processing $total_secrets secrets...\"\n\n        for i in \"${!SECRETS[@]}\"; do\n          secret_name=$(echo \"${SECRETS[$i]}\" | xargs)  # Trim whitespace\n          secret_count=$((secret_count + 1))\n\n          # Get the secret value from environment\n          secret_value=\"${!secret_name}\"\n\n          if [ -n \"$secret_value\" ]; then\n            # Escape JSON special characters in the secret value\n            escaped_value=$(echo \"$secret_value\" | sed 's/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g; s/\\t/\\\\t/g; s/\\r/\\\\r/g; s/\\n/\\\\n/g')\n\n            # Add comma if not the first entry\n            if [ $i -gt 0 ]; then\n              echo \",\" >> \"$OUTPUT_FILE\"\n            fi\n\n            # Add the key-value pair\n            echo -n \"  \\\"$secret_name\\\": \\\"$escaped_value\\\"\" >> \"$OUTPUT_FILE\"\n\n            if [ \"$MASK_VALUES\" = \"true\" ]; then\n              echo \"✓ Added secret: $secret_name (value masked)\"\n            else\n              echo \"✓ Added secret: $secret_name\"\n            fi\n          else\n            echo \"⚠️  Warning: Secret '$secret_name' is empty or not found\"\n\n            # Add comma if not the first entry\n            if [ $i -gt 0 ]; then\n              echo \",\" >> \"$OUTPUT_FILE\"\n            fi\n\n            # Add null value for missing secrets\n            echo -n \"  \\\"$secret_name\\\": null\" >> \"$OUTPUT_FILE\"\n          fi\n        done\n\n        # Close JSON object\n        echo \"\" >> \"$OUTPUT_FILE\"\n        echo \"}\" >> \"$OUTPUT_FILE\"\n\n        echo \"json_file=$OUTPUT_FILE\" >> $GITHUB_OUTPUT\n        echo \"secret_count=$secret_count\" >> $GITHUB_OUTPUT\n\n        echo \"JSON file created: $OUTPUT_FILE\"\n        echo \"Secrets processed: $secret_count\"\n\n        # Show file size\n        file_size=$(wc -c < \"$OUTPUT_FILE\")\n        echo \"File size: $file_size bytes\"\n\n        # Validate JSON syntax\n        if command -v jq >/dev/null 2>&1; then\n          if jq empty \"$OUTPUT_FILE\" 2>/dev/null; then\n            echo \"✓ JSON syntax is valid\"\n          else\n            echo \"❌ JSON syntax is invalid\"\n            exit 1\n          fi\n        else\n          echo \"⚠️  jq not available, skipping JSON validation\"\n        fi\n"
  },
  {
    "path": ".github/actions/mailserver_install/action.yml",
    "content": "name: \"mailserver_install\"\ndescription: \"mailserver_install\"\ninputs:\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n  MAILSERVER_CERT:\n    description: \"Mailserver Certificate\"\n    required: true\n    default: \"None\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Install mailserver\"\n    run: |\n      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n      echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n      sudo apt update\n      sudo apt install -y docker-compose-plugin\n      mkdir -p mailserver/docker-data/certs\n      DMS_GITHUB_URL=\"https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master\"\n      curl ${DMS_GITHUB_URL}/compose.yaml -o mailserver/docker-compose.yaml\n      curl ${DMS_GITHUB_URL}/mailserver.env -o mailserver/mailserver.env\n    shell: bash\n\n  - name: \"Modify downloaded files to reflect test-setup\"\n    run: |\n      echo -e \"networks:\\n  default:\\n    external:\\n      name: ${NAME_SPACE}\" >> mailserver/docker-compose.yaml\n      sudo sed -i \"s/hostname: mail.example.com/hostname: mailserver.${NAME_SPACE}/g\" mailserver/docker-compose.yaml\n      sudo sed -i \"s/- .\\/docker-data\\/dms\\/config\\/:\\/tmp\\/docker-mailserver/- .\\/docker-data\\/dms\\/config\\/:\\/tmp\\/docker-mailserver\\n      - .\\/docker-data\\/certs:\\/etc\\/certs/g\" mailserver/docker-compose.yaml\n      sudo sed -i \"s/ENABLE_OPENDKIM=1/ENABLE_OPENDKIM=0/g\" mailserver/mailserver.env\n      sudo sed -i \"s/ENABLE_OPENDMARC=1/ENABLE_OPENDMARC=0/g\" mailserver/mailserver.env\n      sudo sed -i \"s/ENABLE_POLICYD_SPF=1/ENABLE_POLICYD_SPF=0/g\" mailserver/mailserver.env\n      sudo sed -i \"s/RSPAMD_HFILTER=1/RSPAMD_HFILTER=0/g\" mailserver/mailserver.env\n      sudo sed -i \"s/ENABLE_AMAVIS=1/ENABLE_AMAVIS=0/g\" mailserver/mailserver.env\n      sudo sed -i \"s/SSL_TYPE=/SSL_TYPE=manual/g\" mailserver/mailserver.env\n      sudo sed -i \"s/SSL_CERT_PATH=/SSL_CERT_PATH=\\/etc\\/certs\\/mailserver_crt.pem/g\" mailserver/mailserver.env\n      sudo sed -i \"s/SSL_KEY_PATH=/SSL_KEY_PATH=\\/etc\\/certs\\/mailserver_key.pem/g\" mailserver/mailserver.env\n      cat mailserver/docker-compose.yaml\n      echo ${MAILSERVER_CERT} | base64 -d > mailserver/docker-data/certs/mailserver_crt.pkcs12\n      openssl pkcs12 -in mailserver/docker-data/certs/mailserver_crt.pkcs12 -nodes -nocerts -out mailserver/docker-data/certs/mailserver_key.pem -passin pass:\n      openssl pkcs12 -in mailserver/docker-data/certs/mailserver_crt.pkcs12 -clcerts -nokeys -out mailserver/docker-data/certs/mailserver_crt.pem -passin pass:\n\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      MAILSERVER_CERT: ${{ inputs.MAILSERVER_CERT }}\n\n  - name: \"Start and configure mailserver\"\n    working-directory: mailserver\n    run: |\n      docker compose up -d\n      sleep 20\n      docker exec mailserver grep mydestination /etc/postfix/main.cf\n      docker exec mailserver sh -c \"sed -i 's/mydestination\\s=\\s\\$myhostname,\\slocalhost\\.\\$mydomain,\\slocalhost/mydestination=localhost.\\$mydomain,localhost/g' /etc/postfix/main.cf\"\n      docker exec mailserver grep mydestination /etc/postfix/main.cf\n      docker exec mailserver setup email add postmaster@mailserver.acme pOstmAster\n      docker exec mailserver setup email add a2c@mailserver.acme a2cstarter\n      docker exec mailserver setup email add jum@mailserver.acme jumstarter\n      docker exec mailserver setup email add ulme@mailserver.acme ulmestarter\n    shell: bash\n"
  },
  {
    "path": ".github/actions/mariadb_prep/action.yml",
    "content": "name: \"maria_prep\"\ndescription: \"bring up and configure mariadb instance\"\ninputs:\n  NAME_SPACE:\n    description: \"Name space\"\n    required: true\n    default: \"acme\"\n  INSTANCIATE:\n    description: \"Instanciate mariadb\"\n    required: true\n    default: \"true\"\n  RH_VERSION:\n    description: \"Red Hat version\"\n    required: false\n    default: \"9\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Instanciate Mariadb\"\n      if: inputs.INSTANCIATE == 'true' && inputs.RH_VERSION != '8'\n      run: |\n        echo \"Instanciate mariadb for RH_VERSION $RH_VERSION\"\n        docker run --name mariadbsrv --network acme -e MARIADB_ROOT_PASSWORD=foobar -d mariadb\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n\n    - name: \"Instanciate Mariadb for RHEL8\"\n      if: inputs.INSTANCIATE == 'true' && inputs.RH_VERSION == '8'\n      run: |\n        echo \"Instanciate mariadb 10 for RH_VERSION $RH_VERSION\"\n        docker run --name mariadbsrv --network acme -e MARIADB_ROOT_PASSWORD=foobar -d mariadb:10.6-ubi9\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n\n    - name: \"Sleep for 10s\"\n      if: inputs.INSTANCIATE == 'true'\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Configure mariadb\"\n      working-directory: examples/Docker/\n      run: |\n        docker exec mariadbsrv mariadb -u root --password=foobar -e\"DROP DATABASE IF EXISTS acme2certifier;\"\n        docker exec mariadbsrv mariadb -u root --password=foobar -e\"CREATE DATABASE acme2certifier CHARACTER SET UTF8;\"\n        docker exec mariadbsrv mariadb -u root --password=foobar -e\"GRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY '1mmSvDFl';\"\n        docker exec mariadbsrv mariadb -u root --password=foobar -e\"FLUSH PRIVILEGES;\"\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/mssql_prep/action.yml",
    "content": "name: \"mssql_prep\"\ndescription: \"bring up and configure mssql instance\"\ninputs:\n  NAME_SPACE:\n    description: \"Name space\"\n    required: true\n    default: \"acme\"\n  INSTANCIATE:\n    description: \"Instanciate mssql\"\n    required: true\n    default: \"true\"\n  RH_VERSION:\n    description: \"Red Hat version\"\n    required: false\n    default: \"9\"\n  ROOT_PWD:\n    description: \"MSSQL root password\"\n    required: false\n    default: \"Mssqlpassw0rd\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Instanciate MSSQL\"\n      if: inputs.INSTANCIATE == 'true'\n      run: |\n        docker pull mcr.microsoft.com/mssql/server:2022-latest\n        docker run --rm -d --network ${NAME_SPACE} -e \"ACCEPT_EULA=Y\" -e \"MSSQL_SA_PASSWORD=$ROOT_PWD\"   -p 1433:1433 --name ms-sql --hostname ms-sql mcr.microsoft.com/mssql/server:2022-latest\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n        ROOT_PWD: ${{ inputs.ROOT_PWD }}\n\n    - name: \"Sleep for 10s\"\n      if: inputs.INSTANCIATE == 'true'\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Configure mssql\"\n      working-directory: examples/Docker/\n      run: |\n        docker exec ms-sql /opt/mssql-tools18/bin/sqlcmd -No -S localhost -U SA -P $ROOT_PWD -Q \"IF EXISTS (SELECT name FROM sys.databases WHERE name = 'acme2certifier') DROP DATABASE acme2certifier;\"\n        docker exec ms-sql /opt/mssql-tools18/bin/sqlcmd -No -S localhost -U SA -P $ROOT_PWD -Q \"CREATE DATABASE acme2certifier;\"\n        docker exec ms-sql /opt/mssql-tools18/bin/sqlcmd -S localhost -No -U SA -P $ROOT_PWD -Q \"CREATE LOGIN acme2certifier_user WITH PASSWORD = '$USER_PWD';\"\n        docker exec ms-sql /opt/mssql-tools18/bin/sqlcmd -S localhost -No -U SA -P $ROOT_PWD  -Q \"USE acme2certifier; CREATE USER acme2certifier_user FOR LOGIN acme2certifier_user; ALTER ROLE db_owner ADD MEMBER acme2certifier_user;\"\n      shell: bash\n      env:\n        ROOT_PWD: ${{ inputs.ROOT_PWD }}\n        USER_PWD: 1mmSvDFl\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/parse-json-secret/action.yml",
    "content": "name: 'Parse JSON Secret'\ndescription: 'Parse one or more JSON secrets and create environment variables for each key-value pair'\nauthor: 'grindsa'\n\ninputs:\n  json_secret:\n    description: 'The JSON secret(s) to parse. Can be a single JSON secret or multiple comma-separated JSON secrets'\n    required: true\n  prefix:\n    description: 'Optional prefix for environment variable names'\n    required: false\n    default: ''\n  uppercase:\n    description: 'Convert keys to uppercase'\n    required: false\n    default: 'true'\n\noutputs:\n  variable_count:\n    description: 'Number of variables created'\n    value: ${{ steps.parse.outputs.variable_count }}\n  variable_names:\n    description: 'Comma-separated list of variable names created'\n    value: ${{ steps.parse.outputs.variable_names }}\n\nruns:\n  using: 'composite'\n  steps:\n    - name: Parse JSON secret to environment variables\n      id: parse\n      shell: bash\n      env:\n        JSON_SECRET: ${{ inputs.json_secret }}\n        INPUT_PREFIX: ${{ inputs.prefix }}\n        INPUT_UPPERCASE: ${{ inputs.uppercase }}\n      run: |\n        # Validate inputs\n        if [ -z \"$JSON_SECRET\" ]; then\n          echo \"❌ Error: json_secret input is required\"\n          exit 1\n        fi\n\n        # Validate JSON\n        if ! echo \"$JSON_SECRET\" | jq empty 2>/dev/null; then\n          echo \"❌ Error: Invalid JSON provided\"\n          exit 1\n        fi\n\n        echo \"🔧 Parsing JSON secret...\"\n\n        # Determine prefix\n        PREFIX=\"$INPUT_PREFIX\"\n        if [ -n \"$PREFIX\" ]; then\n          PREFIX=\"${PREFIX}_\"\n        fi\n\n        # Parse JSON and create environment variables\n        # First, get variable names and count for outputs\n        if [ \"$INPUT_UPPERCASE\" = \"true\" ]; then\n          VARIABLE_NAMES=$(echo \"$JSON_SECRET\" | jq -r --arg prefix \"$PREFIX\" '\n            [to_entries[] | \"\\($prefix)\\(.key | ascii_upcase)\"] | join(\",\")\n          ')\n        else\n          VARIABLE_NAMES=$(echo \"$JSON_SECRET\" | jq -r --arg prefix \"$PREFIX\" '\n            [to_entries[] | \"\\($prefix)\\(.key)\"] | join(\",\")\n          ')\n        fi\n\n        # Process each key-value pair individually to handle multi-line values\n        if [ \"$INPUT_UPPERCASE\" = \"true\" ]; then\n          echo \"$JSON_SECRET\" | jq -r --arg prefix \"$PREFIX\" '\n            to_entries[] |\n            [\"\\($prefix)\\(.key | ascii_upcase)\", .value] | @tsv\n          ' | while IFS=$'\\t' read -r var_name var_value; do\n            # Use GitHub's multi-line environment variable syntax\n            EOF_TOKEN=$(openssl rand -hex 8)\n            echo \"${var_name}<<${EOF_TOKEN}\" >> $GITHUB_ENV\n            # Use printf with %b to properly interpret escape sequences like \\n\n            printf '%b\\n' \"$var_value\" >> $GITHUB_ENV\n            echo \"${EOF_TOKEN}\" >> $GITHUB_ENV\n          done\n        else\n          echo \"$JSON_SECRET\" | jq -r --arg prefix \"$PREFIX\" '\n            to_entries[] |\n            [\"\\($prefix)\\(.key)\", .value] | @tsv\n          ' | while IFS=$'\\t' read -r var_name var_value; do\n            # Use GitHub's multi-line environment variable syntax\n            EOF_TOKEN=$(openssl rand -hex 8)\n            echo \"${var_name}<<${EOF_TOKEN}\" >> $GITHUB_ENV\n            # Use printf with %b to properly interpret escape sequences like \\n\n            printf '%b\\n' \"$var_value\" >> $GITHUB_ENV\n            echo \"${EOF_TOKEN}\" >> $GITHUB_ENV\n          done\n        fi\n\n        # Count variables\n        VARIABLE_COUNT=$(echo \"$JSON_SECRET\" | jq '. | length')\n\n        # Set outputs (avoid exposing variable names that might contain sensitive info)\n        echo \"variable_count=$VARIABLE_COUNT\" >> $GITHUB_OUTPUT\n        # Only output variable count, not names to avoid potential exposure\n        echo \"variable_names=***\" >> $GITHUB_OUTPUT\n\n        echo \"✅ Created $VARIABLE_COUNT environment variables\"\n        echo \"📋 Variables created successfully (names hidden for security)\"\n"
  },
  {
    "path": ".github/actions/psql_prep/action.yml",
    "content": "name: \"psql_prep\"\ndescription: \"bring up and configure psql instance\"\ninputs:\n  NAME_SPACE:\n    description: \"Name space\"\n    required: true\n    default: \"acme\"\n  INSTANCIATE:\n    description: \"Instanciate mariadb\"\n    required: true\n    default: \"true\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"postgres environment\"\n      if: inputs.INSTANCIATE == 'true'\n      run: |\n        sudo mkdir -p /tmp/data/pgsql\n        sudo cp .github/a2c.psql /tmp/data/pgsql/a2c.psql\n        sudo cp .github/pgpass /tmp//data/pgsql/pgpass\n        sudo chmod 600 /tmp/data/pgsql/pgpass\n      shell: bash\n\n    - name: \"Install postgres\"\n      if: inputs.INSTANCIATE == 'true'\n      working-directory: /tmp\n      run: |\n        docker run --name postgresdbsrv --network $NAME_SPACE -e POSTGRES_PASSWORD=foobar -d postgres\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Sleep for 10s\"\n      if: inputs.INSTANCIATE == 'true'\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Configure postgres\"\n      if: inputs.INSTANCIATE == 'true'\n      working-directory: /tmp\n      run: |\n        docker run -v \"$(pwd)/data/pgsql/a2c.psql\":/tmp/a2c.psql -v \"$(pwd)/data/pgsql/pgpass:/root/.pgpass\" --rm --network $NAME_SPACE postgres psql -U postgres -h postgresdbsrv -f /tmp/a2c.psql\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Reset postgres\"\n      if: inputs.INSTANCIATE == 'false'\n      working-directory: /tmp\n      run: |\n        docker cp $(pwd)/data/pgsql/a2c.psql postgresdbsrv:/tmp/a2c.psql\n        docker exec postgresdbsrv psql -U postgres -f /tmp/a2c.psql\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/rpm_build/action.yml",
    "content": "name: \"rpm_build\"\ndescription: \"Build RPM package\"\noutputs:\n  rpm_dir_path:\n    description: \"Path to the directory containing the RPM package\"\n    value: ${{ steps.rpm.outputs.rpm_dir_path }}\n  rpm_file_name:\n    description: \"Name of the RPM package file\"\n    value: acme2certifier-${{ env.TAG_NAME }}-1.0.noarch.rpm\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Retrieve Version from version.py\"\n      run: |\n        echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      shell: bash\n\n    - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n      shell: bash\n\n    - name: \"Update version number in spec file and path in nginx ssl config\"\n      run: |\n        sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" examples/install_scripts/rpm/acme2certifier.spec\n        sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\" examples/nginx/nginx_acme_srv_ssl.conf\n        git config --global user.email \"grindelsack@gmail.com\"\n        git config --global user.name \"rpm update\"\n        git add examples/nginx\n        git commit -a -m \"rpm update\"\n      shell: bash\n\n    - name: \"Build RPM package\"\n      id: rpm\n      uses: grindsa/rpmbuild@alma9\n      with:\n        spec_file: \"examples/install_scripts/rpm/acme2certifier.spec\"\n\n    - run: echo \"path is ${{ steps.rpm.outputs.rpm_dir_path }}\"\n      shell: bash\n"
  },
  {
    "path": ".github/actions/rpm_build_upload/action.yml",
    "content": "name: \"rpm_build_upload\"\ndescription: \"Build and Upload package\"\noutputs:\n  rpm_file_name:\n    description: \"Name of the RPM package file\"\n    value: acme2certifier-${{ github.run_id }}.noarch.rpm\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Build rpm package\"\n    id: rpm_build\n    uses: ./.github/actions/rpm_build\n\n  - name: \"Rename rpm package\"\n    run: |\n      sudo mv ${{ steps.rpm_build.outputs.rpm_dir_path }}/noarch/acme2certifier-*.noarch.rpm ${{ steps.rpm_build.outputs.rpm_dir_path }}/noarch/acme2certifier-${{ github.run_id }}.noarch.rpm\n    shell: bash\n\n  - name: \"Upload RPM package\"\n    uses: actions/upload-artifact@v7\n    with:\n      name: acme2certifier-${{ github.run_id }}.noarch.rpm\n      path: ${{ steps.rpm_build.outputs.rpm_dir_path }}/noarch/\n"
  },
  {
    "path": ".github/actions/rpm_prep/action.yml",
    "content": "name: \"rpm_prep\"\ndescription: \"Prepare environment for RPM installation\"\ninputs:\n  GH_USER:\n    description: \"GIT user for SBOM repo\"\n    required: true\n  GH_SBOM_REPO_TOKEN:\n    description: \"GIT token for SBOM repo\"\n    required: true\n  RH_VERSION:\n    description: \"RHEL version\"\n    required: true\n  DJANGO_DB:\n    description: \"Django database\"\n  RPM_BUILD:\n    description: \"Build RPM\"\n    required: true\n    default: \"true\"\n  NAME_SPACE:\n    description: \"Name space\"\n    required: true\n    default: \"acme\"\n  IPV6:\n    description: \"IPv6\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Build rpm package\"\n      if: inputs.RPM_BUILD == 'true'\n      id: rpm_build\n      uses: ./.github/actions/rpm_build\n\n    - name: \"Generate keys and certificates\"\n      uses: ./.github/actions/cert_gen\n\n    - name: \"Setup environment for alma installation\"\n      run: |\n        echo \"IPv6 is $IPV6\"\n        if [ \"$IPV6\" == \"false\" ]; then\n          echo \"create v4 namespace\"\n          docker network create $NAME_SPACE\n        else\n          echo \"create v6 namespace\"\n          docker network create $NAME_SPACE --ipv6 --subnet \"fdbb:6445:65b4:0a60::/64\"\n        fi\n        sudo mkdir -p data/volume\n        sudo mkdir -p data/acme2certifier\n        sudo mkdir -p data/nginx\n        sudo chmod -R 777 data\n        sudo cp examples/Docker/almalinux-systemd/django_tester.sh data\n        sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n        sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n        sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n        if [ -f ${{ steps.rpm_build.outputs.rpm_dir_path }}noarch/acme2certifier-${{ env.TAG_NAME }}-1.0.noarch.rpm ]; then\n          echo \"RPM exists\"\n          sudo cp ${{ steps.rpm_build.outputs.rpm_dir_path }}noarch/acme2certifier-${{ env.TAG_NAME }}-1.0.noarch.rpm data\n        else\n          echo \"RPM does not exist\"\n        fi\n        if [ -z \"$DJANGO_DB\" ] || [ \"$DJANGO_DB\" == \"sqlite3\" ]; then\n            sudo cp .github/django_settings.py data/acme2certifier/settings.py\n        else\n            #if [ \"$RH_VERSION\" == '8' ] && [ \"$DJANGO_DB\" == 'mariadb' ]; then\n            #  echo \"Using psql as django database on RHEL8\"\n            #  sudo cp .github/django_settings_psql.py data/acme2certifier/settings.py\n            #else\n            sudo cp .github/django_settings_$DJANGO_DB.py data/acme2certifier/settings.py\n            #fi\n\n        fi\n        sudo sed -i \"s/\\/var\\/www\\//\\/opt\\//g\" data/acme2certifier/settings.py\n        sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/acme2certifier/settings.py\n      env:\n        DJANGO_DB: ${{ inputs.DJANGO_DB }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        IPV6: ${{ inputs.IPV6 }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n\n      shell: bash\n\n    - run: echo \"RH_VERSION is $RH_VERSION\"\n      env:\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n      shell: bash\n\n    - name: \"Retrieve rpm from SBOM repo\"\n      run: |\n        git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n        cp /tmp/sbom/rpm-repo/RPMs/rhel$RH_VERSION/*.rpm  data\n      env:\n        GH_USER: ${{ inputs.GH_USER }}\n        GH_SBOM_REPO_TOKEN: ${{ inputs.GH_SBOM_REPO_TOKEN }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n      shell: bash\n\n    - name: \"Spin-up alma instance\"\n      run: |\n        docker run -d -id --privileged --network $NAME_SPACE -p 22280:80 --name=acme-srv -v \"$(pwd)/data\":/tmp/acme2certifier almalinux/$RH_VERSION-init\n      env:\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      shell: bash\n\n    - name: \"Instanciate Mariadb\"\n      if: inputs.DJANGO_DB == 'mariadb'\n      uses: ./.github/actions/mariadb_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        RH_VERSION: ${{ inputs.RH_VERSION }}\n\n    - name: \"Instanciate Postgres\"\n      if: inputs.DJANGO_DB == 'psql'\n      uses: ./.github/actions/psql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Prepare addditional environment for MSSQL\"\n      if: inputs.DJANGO_DB == 'mssql'\n      run: |\n        echo \"Download Microsoft repository configuration package\"\n        curl https://packages.microsoft.com/config/rhel/$VERSION/packages-microsoft-prod.rpm --output data/packages-microsoft-prod.rpm\n      env:\n        DJANGO_DB: ${{ inputs.DJANGO_DB }}\n        VERSION: ${{ inputs.RH_VERSION }}\n      shell: bash\n\n    - name: \"Instanciate MSSQL\"\n      if: inputs.DJANGO_DB == 'mssql'\n      uses: ./.github/actions/mssql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/compare_profile_info/action.yml",
    "content": "name: \"compare_profile_info\"\ndescription: \"compare_profile_info\"\ninputs:\n  NAME_SPACE:\n    description: \"Namespace for the test\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Trigger fetch sync of profile information from LE\"\n    run: |\n        docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://acme-srv/directory --insecure\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Get and compare profile information\"\n    run: |\n\n        LE_OUTPUT=$(docker run -i --rm --network $NAME_SPACE curlimages/curl -f https://acme-staging-v02.api.letsencrypt.org/directory --insecure)\n        A2C_OUTPUT=$(docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://acme-srv/directory --insecure)\n\n        # Parse LE_OUTPUT for .meta.profiles using jq\n        LE_PROFILES=$(echo \"$LE_OUTPUT\" | jq '.meta.profiles // empty')\n        A2C_PROFILES=$(echo \"$A2C_OUTPUT\" | jq '.meta.profiles // empty')\n        echo \"LE_PROFILES: $LE_PROFILES\"\n        echo \"A2C_PROFILES: $A2C_PROFILES\"\n\n        if [ \"$LE_PROFILES\" != \"$A2C_PROFILES\" ]; then\n          echo \"Profile information does not match!\"\n          exit 1\n        else\n          echo \"Profile information matches.\"\n        fi\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/compare_renewal_info/action.yml",
    "content": "name: \"compare_profile_info\"\ndescription: \"compare_profile_info\"\ninputs:\n  NAME_SPACE:\n    description: \"Namespace for the test\"\n    required: true\n    default: \"acme\"\n  CERTIFICATE_FILE:\n    description: \"Path to certificate file\"\n    required: false\n    default: \"/tmp/cert.pem\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  CERT_TIMEOUT:\n    description: \"Certificate timeout\"\n    required: true\n    default: \"120\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"HTTPS - Enroll lego\"\n    run: |\n      echo \"##### HTTP - Enroll lego #####\"\n      docker run -i --rm -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify --a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --cert.timeout $CERT_TIMEOUT --tls run\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n  - name: \"Construct renewal info string\"\n    id: construct_renewal_info\n    run: |\n      AKI_HEX=$(sudo openssl x509 -in \"$CERT\" -noout -text | awk '/Authority Key Identifier/{getline; print}' | sed 's/ *keyid://;s/://g')\n      SERIAL_HEX=$(sudo openssl x509 -in \"$CERT\" -noout -serial | cut -d'=' -f2)\n      echo \"$AKI_HEX\" | xxd -r -p > /tmp/aki.bin\n      echo \"$SERIAL_HEX\" | xxd -r -p > /tmp/serial.bin\n      AKI_B64=$(openssl base64 -A -in /tmp/aki.bin | tr '+/' '-_' | tr -d '=')\n      SERIAL_B64=$(openssl base64 -A -in /tmp/serial.bin | tr '+/' '-_' | tr -d '=')\n      RENEWAL_INFO=\"${AKI_B64}.${SERIAL_B64}\"\n      echo \"RENEWAL_INFO=$RENEWAL_INFO\" >> $GITHUB_OUTPUT\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      CERT: ${{ inputs.CERTIFICATE_FILE }}\n\n  - name: \"Get and compare renewal strings from a2c and LE\"\n    run: |\n      echo \"LEGO RENEWAL INFO: $RENEWAL_INFO\"\n\n      LE_RENEWAL_OUTPUT=$(docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://acme-staging-v02.api.letsencrypt.org/acme/renewal-info/$RENEWAL_INFO)\n      A2C_RENEWAL_OUTPUT=$(docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/acme/renewal-info/$RENEWAL_INFO)\n      LE_RENEWAL_INFO=$(echo \"$LE_RENEWAL_OUTPUT\" | jq | sha224sum)\n      A2C_RENEWAL_INFO=$(echo \"$A2C_RENEWAL_OUTPUT\" | jq | sha224sum)\n      echo \"LE_RENEWAL: $LE_RENEWAL_INFO\"\n      echo \"A2C_RENEWAL: $A2C_RENEWAL_INFO\"\n\n      if [ -z \"$LE_RENEWAL_INFO\" ] || [ -z \"$A2C_RENEWAL_INFO\" ]; then\n        echo \"One of the renewal info values is empty (None)!\"\n        exit 1\n      fi\n\n      if [ \"$LE_RENEWAL_INFO\" != \"$A2C_RENEWAL_INFO\" ]; then\n        echo \"Renewal information does not match!\"\n        exit 1\n      else\n        echo \"Renewal information matches.\"\n      fi\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      RENEWAL_INFO: ${{ steps.construct_renewal_info.outputs.RENEWAL_INFO }}\n\n  - name: \"HTTPS - Revoke lego\"\n    if: ${{ inputs.REVOCATION == 'true' }}\n    run: |\n      echo \"#### HTTPS - Revoke lego\"\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE revoke\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n\n\n  - name: \"Get and compare profile information\"\n    run: |\n      echo \"$RENEWAL_INFO\"\n\n      #if [ \"$LE_PROFILES\" != \"$A2C_PROFILES\" ]; then\n      #  echo \"Profile information does not match!\"\n      #  exit 1\n      #else\n      #  echo \"Profile information matches.\"\n      #fi\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Delete lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_acmeprofile\"\ndescription: \"enroll_acmeprofile‚\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Enroll lego with without template\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 02 - Enroll lego with a unknown template_name\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"ACME Profile - 02 - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME Profile - 02 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep unknown\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 03 - Enroll lego with am allowed template_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile profile2\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME Profile - 03 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"profile: profile2\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"profile: profile2\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/enroll_dns/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  REVOCATION:\n    description: \"Revocation method\"\n    required: true\n    default: \"true\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  VERIFY_CERT:\n    description: \"Verify certificate\"\n    required: true\n    default: \"true\"\n  USE_CERTBOT:\n    description: \"Use certbot\"\n    required: true\n    default: \"true\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n  TEST_ADL:\n    description: \"Test allowed_domainlist feature\"\n    required: true\n    default: \"false\"\n  CERT_TIMEOUT:\n    description: \"Certificate timeout\"\n    required: true\n    default: \"120\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acme-sh/\n        sudo mkdir -p certbot/\n        sudo mkdir -p lego/ca\n        sudo cp .github/acme2certifier_cabundle.pem certbot/\n        sudo cp .github/acme2certifier_cabundle.pem lego/\n        if [ -f cert-2.pem ]; then\n          echo \"delete cert-2.pem\"\n          rm -f cert-2.pem\n        fi\n        if [ -f cert-1.pem ]; then\n          echo \"delete cert-1.pem\"\n          rm -f cert-1.pem\n        fi\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll lego\"\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify --a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --cert.timeout $CERT_TIMEOUT --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify --a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --cert.timeout $CERT_TIMEOUT --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n    - name: \"HTTPS - Revoke lego\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"#### HTTPS - Revoke lego\"\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE revoke\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll certbot\"\n      if: ${{ inputs.USE_CERTBOT == 'true' }}\n      run: |\n        echo \"##### HTTPS - Enroll certbot #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout $CERT_TIMEOUT\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout $CERT_TIMEOUT\n        fi\n\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n          else\n            echo \"single root ca\"\n            sudo openssl verify -CAfile cert-1.pem certbot/live/certbot/cert.pem\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n    - name: \"HTTPS - Revoke certbot\"\n      if: ${{ (inputs.USE_CERTBOT == 'true') && (inputs.REVOCATION == 'true') }}\n      run: |\n        echo \"##### HTTPS - Revoke certbot #####\"\n        docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --server https://$ACME_SERVER:$HTTPS_PORT --no-verify-ssl --delete-after-revoke --cert-name certbot\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n      continue-on-error: true\n      id: legofail01\n      if: ${{ inputs.TEST_ADL == 'true' }}\n      run: |\n        echo \"##### HTTP - Enroll lego to test allowed domainlist feature #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Allowed domainlist feature - check  result \"\n      if: ${{ (inputs.TEST_ADL == 'true') && steps.legofail01.outcome != 'failure' }}\n      run: |\n        echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Delete acme-sh, letsencypt and lego folders\"\n      run: |\n        sudo rm -rf  lego/*\n        sudo rm -rf  acme-sh/*\n        sudo rm -rf  certbot/*\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/enroll_dns_wc/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  REVOCATION:\n    description: \"Revocation method\"\n    required: true\n    default: \"true\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  VERIFY_CERT:\n    description: \"Verify certificate\"\n    required: true\n    default: \"true\"\n  USE_CERTBOT:\n    description: \"Use certbot\"\n    required: true\n    default: \"true\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n  TEST_ADL:\n    description: \"Test allowed_domainlist feature\"\n    required: true\n    default: \"false\"\n  CERT_TIMEOUT:\n    description: \"Certificate timeout\"\n    required: true\n    default: \"120\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acme-sh/\n        sudo mkdir -p certbot/\n        sudo mkdir -p lego/ca\n        sudo cp .github/acme2certifier_cabundle.pem certbot/\n        sudo cp .github/acme2certifier_cabundle.pem lego/\n        if [ -f cert-2.pem ]; then\n          echo \"delete cert-2.pem\"\n          rm -f cert-2.pem\n        fi\n        if [ -f cert-1.pem ]; then\n          echo \"delete cert-1.pem\"\n          rm -f cert-1.pem\n        fi\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll lego\"\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d *.$NAME_SPACE --cert.timeout $CERT_TIMEOUT --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" --key-type=rsa2048 -d *.$NAME_SPACE --cert.timeout $CERT_TIMEOUT --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n    - name: \"HTTPS - Revoke lego\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"#### HTTPS - Revoke lego\"\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT --tls-skip-verify -a --email \"lego@example.com\" -d *.$NAME_SPACE revoke\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"eab_acmeprofile\"\ndescription: \"eab_acmeprofile\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego without profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"profile: profile_1\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"profile: profile_1\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB with ACME profile - 02a - Enroll lego with a profile taken NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB with ACME profile - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with ACME profile - 02b - Enroll lego with a profile included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile profile_2\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        echo \"container deployment\"\n        docker compose logs | grep \"profile: profile_2\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"profile: profile_2\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB with ACME profile - 03 - Enroll lego with a profile/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"profile: profile_2\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"profile: profile_2\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB with ACME profile - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with ACME profile - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with ACME profile - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i sub-ca\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"profile: profile_1\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"profile: profile_1\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/enrollment_profiling/action.yml",
    "content": "name: \"enrollment_profiling\"\ndescription: \"le-enrollment_profiling\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll acme.sh without acme_url\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -issuer --noout | grep -i root-ca\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego without acme_url\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i root-ca\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - Enroll acme with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent acme_url=http://foo.bar -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll acme with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent acme_url=http://acme-le-sim-1.acme -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -issuer --noout | grep -i sub-ca\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent acme_url=http://foo.bar -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent acme_url=http://acme-le-sim-1.acme -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i sub-ca\n    shell: bash\n\n  - name: \"EAB - 03 - Enroll acme with a acme_url and key taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -issuer --noout | grep -i root-ca\n    shell: bash\n\n  - name: \"EAB without headerinfo - 03 - Enroll lego with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i root-ca\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    id: acmefail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh. --standalone --keylength 2048 --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - check  result \"\n    if: steps.acmefail02.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i sub-ca\n    shell: bash\n\n  - name: \"EAB with headerinfo - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i sub-ca\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/le-sim_prep/action.yml",
    "content": "name: \"le-sim_prep\"\ndescription: \"le-sim_prep\"\ninputs:\n  LESIM_NAME:\n    description: \"Name of the le-sim\"\n    required: true\n    default: \"acme-le-sim\"\n  NAME_SPACE:\n    description: \"Name space of the le-sim\"\n    required: true\n    default: \"acme\"\n  SECTIGO_SIM:\n    description: \"Sectigo sim\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Setup le-sim\"\n    run: |\n      sudo mkdir -p \"$LESIM_NAME/acme_ca/certs\"\n      sudo cp examples/ca_handler/openssl_ca_handler.py \"$LESIM_NAME/ca_handler.py\"\n      sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem \"$LESIM_NAME/acme_ca/\"\n      sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg \"$LESIM_NAME/acme_srv.cfg\"\n      sudo chmod 777 \"$LESIM_NAME/acme_srv.cfg\"\n      if [ \"$SECTIGO_SIM\" == \"true\" ]; then\n        echo \"Sectigo sim enabled\"\n        sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True\\nsectigo_sim: True/g\" \"$LESIM_NAME/acme_srv.cfg\"\n      fi\n      sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" \"$LESIM_NAME/acme_srv.cfg\"\n      docker run -d --rm -id --network \"$NAME_SPACE\" --name=\"$LESIM_NAME\" -v \"$(pwd)/$LESIM_NAME\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:apache2-wsgi\n      cat \"$LESIM_NAME/acme_srv.cfg\"\n    shell: bash\n    env:\n      LESIM_NAME: ${{ inputs.LESIM_NAME }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      SECTIGO_SIM: ${{ inputs.SECTIGO_SIM }}\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-le-sim/directory is accessible\"\n    run: docker run -i --rm --network \"$NAME_SPACE\" curlimages/curl -f http://\"$LESIM_NAME\"/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      LESIM_NAME: ${{ inputs.LESIM_NAME }}\n\n  - name: \"Enroll from le-sim\"\n    run: |\n      mkdir -p acme-sh/\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://\"$LESIM_NAME\" --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      sudo rm -rf acme-sh/*\n    shell: bash\n    env:\n      LESIM_NAME: ${{ inputs.LESIM_NAME }}\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_ca_handler/smallstep_prep/action.yml",
    "content": "name: \"smallstep_prep\"\ndescription: \"smallstep_prep\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Setup smallstep\"\n    run: |\n      sudo mkdir -p step\n      sudo chmod -R 777 step\n      docker run -d -v \"$(pwd)/step\":/home/step \\\n          -p 9000:9000 -p 443:443 \\\n          --network acme \\\n          --name step-ca \\\n          -e \"DOCKER_STEPCA_INIT_NAME=Smallstep\" \\\n          -e \"DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)\" \\\n          smallstep/step-ca\n    shell: bash\n\n  - name: \"Sleep for 20s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 20s\n\n  - name: \"Configure smallstep\"\n    run: |\n      docker ps\n      docker exec -i step-ca step ca provisioner add acme --type ACME\n      docker exec -i step-ca step ca provisioner update acme --remove-challenge=tls-alpn-01\n      docker exec -i step-ca step ca provisioner update acme --remove-challenge=dns-01\n      docker restart step-ca\n    shell: bash\n\n  - name: \"Sleep for 20s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 20s\n\n  - name: \"Test https://step-ca.acme/acme/acme/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f https://step-ca:9000/acme/acme/directory --insecure\n    shell: bash\n\n  - name: \"Enroll from smallstep using acme-sh\"\n    run: |\n      mkdir -p acme-sh\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server https://step-ca:9000/acme/acme/directory --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --insecure --output-insecure --force\n      sudo rm -rf acme-sh/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/acme_sh/enroll/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  KEYLENGTH:\n    description: \"Key length to use for the certificate\"\n    required: true\n    default: \"2048\"\n  ACCOUNTKEYLENGTH:\n    description: \"Account key length to use for the certificate\"\n    required: true\n    default: \"2048\"\n  CA_PATH:\n    description: \"Path to CA certificates\"\n    required: false\n    default: \"examples/Docker/data/acme_ca/\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Create folders\"\n      run: |\n        mkdir acme-sh\n      shell: bash\n\n    - name: \"Test http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network acme curlimages/curl -f http://$ACME_SERVER/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network acme curlimages/curl --insecure -f https://$ACME_SERVER/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"Prepare acme.sh container\"\n      run: |\n        docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n      shell: bash\n\n    - name: \"Enroll HTTP-01 single domain acme.sh\"\n      run: |\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --keylength $KEYLENGTH --accountkeylength $ACCOUNTKEYLENGTH --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"_ecc\"\n        fi\n        openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n        ACCOUNTKEYLENGTH: ${{ inputs.ACCOUNTKEYLENGTH }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n\n    - name: \"Renew HTTP-01 single domain acme.sh\"\n      run: |\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"--ecc\"\n        fi\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --keylength $KEYLENGTH --renew --force ${ECC} -d acme-sh.acme --standalone --debug 3 --output-insecure\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"_ecc\"\n        fi\n        openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n\n    - name: \"Revoke HTTP-01 single domain acme.sh\"\n      run: |\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"--ecc\"\n        fi\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --revoke ${ECC} -d acme-sh.acme --standalone --debug 2 --output-insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n\n    - name: \"Enroll HTTP-01 2x domain acme.sh\"\n      run: |\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --keylength $KEYLENGTH --issue -d acme-sh.acme -d acme-sh. --standalone --debug 3 --output-insecure\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"_ecc\"\n        fi\n        openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n\n    - name: \"Renew HTTP-01 2x domain acme.sh\"\n      run: |\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"--ecc\"\n        fi\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --keylength $KEYLENGTH --renew --force ${ECC} -d acme-sh.acme -d acme-sh. --standalone --debug 3 --output-insecure\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"_ecc\"\n        fi\n        openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n\n    - name: \"Revoke HTTP-01 2x domain acme.sh\"\n      run: |\n        if ([ \"$KEYLENGTH\" == \"ec-256\" ] || [ \"$KEYLENGTH\" == \"ec-384\" ] || [ \"$KEYLENGTH\" == \"ec-521\" ]) ; then\n          ECC=\"--ecc\"\n        fi\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --revoke ${ECC} -d acme-sh.acme -d acme-sh. --standalone --debug 3 --output-insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        KEYLENGTH: ${{ inputs.KEYLENGTH }}\n\n    - name: \"Deactivate acme.sh\"\n      run: |\n        docker exec -i acme-sh acme.sh --server http://$ACME_SERVER --deactivate-account --debug 2 --output-insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ari/enroll/action.yml",
    "content": "name: \"ari tests - enroll acme clients\"\ndescription: \"Test ARI feature - enroll acme clients against acme-srv using acme.sh\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  CA_PATH:\n    description: \"Path to CA certificates\"\n    required: false\n    default: \"examples/Docker/data/acme_ca/\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Create lego folder\"\n      run: |\n        mkdir lego\n      shell: bash\n\n    - name: \"Test http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network acme curlimages/curl -f http://$ACME_SERVER/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network acme curlimages/curl --insecure -f https://$ACME_SERVER/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"Enroll lego\"\n      run: |\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://$ACME_SERVER -a --email \"lego@example.com\" -d lego.acme --http run\n        sudo openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem lego/certificates/lego.acme.crt\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n\n    - name: \"Renew lego\"\n      run: |\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://$ACME_SERVER -a --email \"lego@example.com\" -d lego.acme --http renew --no-random-sleep 2> ari.txt\n        grep \"renewalInfo endpoint indicates that renewal is needed\" ari.txt\n        cat ari.txt\n        sudo openssl verify -CAfile $CA_PATH/root-ca-cert.pem -untrusted $CA_PATH/sub-ca-cert.pem lego/certificates/lego.acme.crt\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        CA_PATH: ${{ inputs.CA_PATH }}\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\ninputs:\n  ASA_PROFILE1:\n    description: \"ASA Profile 1\"\n    required: true\n  ASA_PROFILE2:\n    description: \"ASA Profile 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"ACME Profiling - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profiling - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profiling - 01 - Enroll lego with Profile 1\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --key-type rsa2048 --http run --profile \"$ASA_PROFILE1\"\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      # sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n    env:\n      ASA_PROFILE1: ${{ inputs.ASA_PROFILE1 }}\n    shell: bash\n\n  - name: \"ACME Profiling - 02 - Enroll lego with Profile 2\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --key-type rsa2048 --http run --profile \"$ASA_PROFILE2\"\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      # sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    env:\n      ASA_PROFILE2: ${{ inputs.ASA_PROFILE2 }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n  ASA_PROFILE1:\n    description: \"ASA Profile 1\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB ACME Profiling - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB ACME Profiling - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB ACME Profiling - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 01 - Enroll lego without profile_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -k rsa2048 -d lego.acme --http run\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB ACME Profiling - 02a - Enroll lego with a profile_name NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -k rsa2048 -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 02b - Enroll lego with a profile_name included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -k rsa2048 -d lego.acme --http run --profile \"$ASA_PROFILE1\"\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_PROFILE1: ${{ inputs.ASA_PROFILE1 }}\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB ACME Profiling - 03 - Enroll lego with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme --http run\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME2\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep -i \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME2: ${{ inputs.ASA_CA_NAME2 }}\n\n  - name: \"EAB ACME Profiling - 03 - Revoke lego with a profile_name/ca_name taken from kid.json\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme revoke\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail021\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 04a - check  result \"\n    if: steps.legofail021.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail021.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -k rsa2048 -d lego.acme --http run\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB ACME Profiling - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB ACME Profiling - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_eab_w_headerinfo/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB with headerinfo - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01 - Enroll acme.sh without profile_name\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 01 - Enroll lego without profile_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 02a - Enroll acme with a profile_name taken from header_info NOT included in kid.json (to fail)\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_name=unknown -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll acme with a profile_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_name=ACME -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 02a - Enroll lego with a profile_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_name=unknown -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll lego with a profile_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_name=ACME -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 03 - Enroll acme with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone  --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME2\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep -i \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME2: ${{ inputs.ASA_CA_NAME2 }}\n\n  - name: \"EAB with headerinfo - 03 - Enroll lego with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME2\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep -i \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME2: ${{ inputs.ASA_CA_NAME2 }}\n\n  - name: \"EAB with headerinfo - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    id: acmefail021\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - check  result \"\n    if: steps.acmefail021.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail021.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail021\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04a - check  result \"\n    if: steps.legofail021.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail021.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB with headerinfo - 06 - Enroll acme with not allowed headerinfo-field (should fail)\"\n    id: acmefail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_id=101 -d acme-sh.acme --keylength 2048  --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - check  result \"\n    if: steps.acmefail03.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --user-agent profile_id=101 -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_eab_wo_headerinfo/action.yml",
    "content": "name: \"enroll_wo_headerinfo\"\ndescription: \"enroll_wo_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB without headerinfo -  Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB without headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo -  Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo - 01 - Enroll acme.sh without profile_name\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB without headerinfo - 01 - Enroll lego without profile_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB without headerinfo - 02 - Enroll acme with a profile_name taken from header_info NOT included in kid.json (to be ignored)\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_name=unknown -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep -i \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB without headerinfo - 02 - Enroll lego  with a profile_name taken from header_info NOT included in kid.json (to be ignored)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_name=unknown -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB without headerinfo - 03 - Enroll acme with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone  --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME2\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep -i \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME2: ${{ inputs.ASA_CA_NAME2 }}\n\n  - name: \"EAB without headerinfo - 03 - Enroll lego with a profile_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg  -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME2\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep -i \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME2: ${{ inputs.ASA_CA_NAME2 }}\n\n  - name: \"EAB without headerinfo - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    id: acmefail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - check  result \"\n    if: steps.acmefail02.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -k rsa2048 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB without headerinfo - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n\n  - name: \"EAB without headerinfo - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -k rsa2048 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"$ASA_CA_NAME1\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_CA_NAME1: ${{ inputs.ASA_CA_NAME1 }}\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_headerinfo/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\ninputs:\n  ASA_PROFILE1:\n    description: \"ASA Profile 1\"\n    required: true\n  ASA_PROFILE2:\n    description: \"ASA Profile 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Header-info - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - 01 - Enroll acme.sh with Profile 1\"\n    run: |\n      sudo rm  -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' --useragent \"profile_name=$ASA_PROFILE1\" -d acme-sh.acme --alpn --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -texte -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_PROFILE1: ${{ inputs.ASA_PROFILE1 }}\n\n  - name: \"Header-info - 01 - Enroll lego with Profile 1\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent \"profile_name=$ASA_PROFILE1\" -d lego.acme --key-type rsa2048 --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n    env:\n      ASA_PROFILE1: ${{ inputs.ASA_PROFILE1 }}\n\n  - name: \"Header-info - 02 - Enroll acme.sh with Profile 2\"\n    run: |\n      sudo rm  -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' --useragent profile_name=\"$ASA_PROFILE2\" -d acme-sh.acme --alpn --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -texte -noout\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    env:\n      ASA_PROFILE2: ${{ inputs.ASA_PROFILE2 }}\n    shell: bash\n\n  - name: \"Header-info - 02 - Enroll lego with Profile 2\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_name=\"$ASA_PROFILE2\" -d lego.acme --key-type rsa2048 --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      # sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    env:\n      ASA_PROFILE2: ${{ inputs.ASA_PROFILE2 }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_profile_1/action.yml",
    "content": "name: \"enroll_profile_1\"\ndescription: \"wf enroll_profile_1\"\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Profile 1 - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Profile 1 - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 1 - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 1 - Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n    shell: bash\n\n  - name: \"Profile 1 - Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  #- name: \"Profile 1 - Register certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n  #  shell: bash\n\n  #- name: \"Profile 1 - Enroll HTTP-01 single domain certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot  --key-type rsa --rsa-key-size 2048\n  #    sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n  #    sudo openssl x509 -in certbot/live/certbot/cert.pem -ext keyUsage -noout | grep \"Digital Signature\"\n  #    # sudo openssl x509 -in certbot/live/certbot/cert.pem -text -noout\n  #  shell: bash\n\n  #- name: \"Profile 1 - Revoke HTTP-01 single domain certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n  #  shell: bash\n\n  - name: \"Profile 1 - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme  --key-type rsa2048 --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n      # sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Profile 1 - revoke HTTP-01 single domain lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n\n  - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n    continue-on-error: true\n    id: legofail01\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego  --key-type rsa2048 --http run\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      USE_RSA: ${{ inputs.USE_RSA }}\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Allowed domainlist feature - check  result \"\n    if: ${{ steps.legofail01.outcome != 'failure' }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n"
  },
  {
    "path": ".github/actions/wf_specific/asa_ca_handler/enroll_profile_2/action.yml",
    "content": "name: \"enroll_2_profile\"\ndescription: \"wf enrollment 2 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Profile 2 - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Profile 2 -  create letsencrypt and lego folder\"\n    run: |\n      sudo rm -rf certbot/*\n      sudo rm -rf lego/*\n      sudo rm -rf acme-sh/*\n    shell: bash\n\n  - name: \"Profile 2 - Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --keylength 2048 --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    shell: bash\n\n  - name: \"Profile 2 - Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  #- name: \"Profile 2 - Register certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n  #  shell: bash\n\n  #- name: \"Profile 2 - Enroll HTTP-01 single domain certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot --force-renewal --key-type rsa --rsa-key-size 2048\n  #    sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n  #    sudo openssl x509 -in certbot/live/certbot/cert.pem -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n  #  shell: bash\n\n  #- name: \"Profile 2 - Revoke HTTP-01 single domain certbot\"\n  #  run: |\n  #    docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n  #  shell: bash\n\n  - name: \"Profile 2 - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme  --key-type rsa2048 --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n    shell: bash\n\n  - name: \"Profile 2 - Revoke HTTP-01 single domain lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_101_profile/action.yml",
    "content": "name: \"enroll_101_profile\"\ndescription: \"wf enrollment 101 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Profile 101 - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Profile 101 - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 101 - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 101 - Enroll acme.sh\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n    shell: bash\n\n  - name: \"Profile 101 - Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"Profile 101 - Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Profile 101 - Enroll HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/live/certbot/cert.pem -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n\n  - name: \"Profile 101 - Revoke HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n\n  - name: \"Profile 101 - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n\n  - name: \"Profile 101 - Revoke HTTP-01 single domain lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_102_profile/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Profile 102 - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Profile 102 - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 102 - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Profile 102 - Enroll acme.sh\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n    shell: bash\n\n  - name: \"Profile 102 - Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"Profile 102 - Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Profile 102 - Enroll HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/live/certbot/cert.pem -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n\n  - name: \"Profile 102 - Revoke HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n\n  - name: \"Profile 102 - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n\n  - name: \"Profile 102 - Revoke HTTP-01 single domain lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Enroll lego with profile_id 101\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile 101\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n\n  - name: \"ACME Profile - 02 - Enroll lego with profile_id 102\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile 102\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n\n  - name: \"ACME Profile - 03 - Enroll lego with unknown profile_id\"\n    id: legoprofilefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      # sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n\n  - name: \"EAB - 03 - check  result \"\n    if: steps.legoprofilefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.legoprofilefail01.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_acmeprofile/action.yml",
    "content": "name: \"enroll_acme_profile\"\ndescription: \"wf enrollment acme profile\"\ninputs:\n  RECONFIGURE:\n    description: \"Reconfigure the workflow\"\n    required: true\n    default: \"false\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB with ACME Profile - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with ACME Profile - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 01 - Enroll lego without profile_id\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with ACME Profile - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Check issuance log entry\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"issued for account\" | grep \"with EAB KID keyid_00.\" | grep \"Serial:\" | grep \"Common Name: lego.acme, SANs: \\['DNS:lego.acme'\\]\"\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv bash -c 'tail -n 500 /var/log/messages | grep \"issued for account\" | grep \"with EAB KID keyid_00.\" | grep \"Serial:\" | grep \"Common Name: lego.acme, SANs:\"'\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB with ACME Profile - 01 - revoke lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme revoke\n    shell: bash\n\n  - name: \"EAB with ACME Profile - Sleep for 20s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 20s\n\n  - name: \"Check Revocation log entry\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"revocation successful for account\" | grep \"with EAB KID\" | grep \"Serial\" | grep \"Common Name: lego.acme, SANs: \\['DNS:lego.acme'\\]\"\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv bash -c 'tail -n 500 /var/log/messages | grep \"revocation successful for account\" | grep \"with EAB KID keyid_00.\"| grep \"Serial:\" | grep \"Common Name: lego.acme, SANs:\"'\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB with ACME Profile - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile 101\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep -i \"CN = SubCA2\"\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 03 - Revoke lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme revoke\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    if: ${{ inputs.RECONFIGURE == 'false' }}\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 04a - check  result\"\n    if: ${{ (inputs.RECONFIGURE == 'false') && (steps.legofail02.outcome != 'failure') }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 04 - Enroll legowith a allowed fqdn after reconfiguration\"\n    if: ${{ inputs.RECONFIGURE == 'true' }}\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"Code Signing\"\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run --profile 101\n    shell: bash\n\n  - name: \"EAB with ACME Profile - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\ninputs:\n  RECONFIGURE:\n    description: \"Reconfigure the workflow\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB with headerinfo - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01 - Enroll acme.sh without profile_id\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01 - Enroll lego without profile_id\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - Enroll acme with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_id=unknown -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll acme with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_id=101 -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_id=unknown -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_id=101 -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 03 - Enroll acme with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    if: ${{ inputs.RECONFIGURE == 'false' }}\n    id: acmefail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - check  result \"\n    if: ${{ (inputs.RECONFIGURE == 'false') && (steps.acmefail02.outcome != 'failure') }}\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll acme with a allowed fqdn after reconfiguration\"\n    if: ${{ inputs.RECONFIGURE == 'true' }}\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    if: ${{ inputs.RECONFIGURE == 'false' }}\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04a - check  result\"\n    if: ${{ (inputs.RECONFIGURE == 'false') && (steps.legofail02.outcome != 'failure') }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Enroll legowith a allowed fqdn after reconfiguration\"\n    if: ${{ inputs.RECONFIGURE == 'true' }}\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"Code Signing\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"Code Signing\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - Enroll acme with not allowed headerinfo-field (should fail)\"\n    id: acmefail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_id=101 -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - check  result \"\n    if: steps.acmefail03.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --user-agent profile_id=101 -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB with headerinfo - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_eab_wo_headerinfo/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB without headerinfo - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB without headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo - Enroll acme.sh without profile_id\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - Enroll lego without profile_id\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 02 - Enroll acme with a template_name taken from header_info NOT included in kid.json (to be ignored)\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent profile_id=unknown -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep -i \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 02 - Enroll lego with a template_name taken from header_info NOT included in kid.json (to be ignored)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_id=unknown -d lego.acme --http run\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent profile_id=101 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 03 - Enroll acme with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    id: acmefail021\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - check  result \"\n    if: steps.acmefail021.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail021.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail021\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04a - check  result \"\n    if: steps.legofail021.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail021.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB without headerinfo - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"Code Signing\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"Code Signing\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_headerinfo/action.yml",
    "content": "name: \"enroll_102_profile\"\ndescription: \"wf enrollment 102 profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Header-info - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - 01 - Enroll acme.sh with profile_id 101\"\n    run: |\n      sudo rm  -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' --useragent profile_id=101 -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n\n  - name: \"Header-info - 01 - Enroll lego with profile_id 101\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_id=101 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n\n  - name: \"Header-info - 02 - Enroll acme.sh with profile_id 102\"\n    run: |\n      sudo rm  -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' --useragent profile_id=102 -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n\n  - name: \"Header-info - 02 - Enroll lego with profile_id 102\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_id=102 -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/enroll_no_profile/action.yml",
    "content": "name: \"enroll_no_profile\"\ndescription: \"wf enrollment without profile\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Create folders\"\n    run: |\n      sudo mkdir -p lego\n      sudo mkdir -p acme-sh\n      sudo mkdir -p certbot\n    shell: bash\n\n  - name: \"No profile - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"No profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"No profile - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"No profile - Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n    shell: bash\n\n  - name: \"No profile - Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"No profile - Register certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"No profile - Enroll HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n    shell: bash\n\n  - name: \"No profile - Revoke HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n\n  - name: \"No profile - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"No profile - Revoke HTTP-01 single domain lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/certifier_ca_handler/tunnel_setup/action.yml",
    "content": "name: \"tunnel_setup\"\ndescription: \"tunnel_setup\"\ninputs:\n  SSH_KEY:\n    description: \"SSH access key\"\n    required: true\n  SSH_KNOWN_HOSTS:\n    description: \"SSH known hosts\"\n    required: true\n  SSH_USER:\n    description: \"SSH user\"\n    required: true\n  SSH_HOST:\n    description: \"SSH host\"\n    required: true\n  SSH_PORT:\n    description: \"SSH port\"\n    required: true\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  NCM_API_HOST:\n    description: \"NCM API host\"\n    required: true\n  NCM_API_USER:\n    description: \"NCM API user\"\n    required: true\n  NCM_API_PASSWORD:\n    description: \"NCM API password\"\n    required: true\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Prepare ssh environment on ramdisk \"\n    run: |\n      sudo mkdir -p /tmp/rd\n      sudo mount -t tmpfs -o size=5M none /tmp/rd\n      sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n      sudo chmod 600 /tmp/rd/ak.tmp\n      sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n    env:\n      SSH_KEY: ${{ inputs.SSH_KEY }}\n      KNOWN_HOSTS: ${{ inputs.SSH_KNOWN_HOSTS }}\n    shell: bash\n\n  - name: \"Setup ssh forwarder\"\n    run: |\n        docker run -d --rm --network $NAME_SPACE --name=forwarder  -e \"MAPPINGS=8084:127.0.0.1:8084\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 8080:8084 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n    env:\n      SSH_USER: ${{ inputs.SSH_USER }}\n      SSH_HOST: ${{ inputs.SSH_HOST }}\n      SSH_PORT: ${{ inputs.SSH_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      NCM_API_HOST: ${{ inputs.NCM_API_HOST }}\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test conection to mscertsrv via ssh tunnel\"\n    run: |\n      docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure https://$NCM_API_USER:$NCM_API_PASSWORD@forwarder.acme:8084\n    env:\n      NCM_API_HOST: ${{ inputs.NCM_API_HOST }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      NCM_API_USER: ${{ inputs.NCM_API_USER }}\n      NCM_API_PASSWORD: ${{ inputs.NCM_API_PASSWORD }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/digicert_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Enroll lego with without template\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -text -noout\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 02 - Enroll lego with a unknown template_name taken from profile\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run --profile unknown\n    shell: bash\n\n  - name: \"ACME Profile - 02 - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"ACME Profile - 02 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 250 /var/log/messages | grep unknown\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 03 - Enroll lego with am allowed template_name taken from profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run --profile ssl_securesite_pro\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"ACME Profile - 03 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_type: ssl_securesite_pro\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"cert_type: ssl_securesite_pro\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/digicert_ca_handler/enroll_eab/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -text -noout\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"EAB - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent cert_type=unknown -d lego.acme.dynamop.de --http run\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent cert_type=ssl_securesite_pro -d lego.acme.dynamop.de --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"EAB - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"EAB - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme1.dynamop.de --http run\n    shell: bash\n\n  - name: \"EAB - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"EAB - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --user-agent cert_type=ssl_securesite_pro -d lego.acme.dynamop.de --http run\n    shell: bash\n\n  - name: \"EAB - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/digicert_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme.dynamop.de curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -text -noout\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_type: ssl_basic\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"cert_type: ssl_basic\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        sleep 5\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"unknown\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de --http run --profile ssl_securesite_pro\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.dynamop.de.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_type: ssl_securesite_pro\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"cert_type: ssl_securesite_pro\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_type: ssl_securesite_pro\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"cert_type: ssl_securesite_pro\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme1.dynamop.de --http run\n    shell: bash\n\n  - name: \"EAB - 04 - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme.dynamop.de --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.dynamop.de.crt\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme.dynamop.de goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme.dynamop.de revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/disable_challengevalidation/dehydrated_install/action.yml",
    "content": "name: \"dehydrated_install\"\ndescription: \"dehydrated_install\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Install dehydrated\"\n    run: |\n      git clone https://github.com/dehydrated-io/dehydrated\n      cd dehydrated/\n      mkdir -p /tmp/dehydrated/.well-known/acme-challenge\n      echo 'CHALLENGETYPE=\"http-01\"' > config\n      echo 'WELLKNOWN=\"/tmp/dehydrated/.well-known/acme-challenge\"' >> config\n      echo 'CONTACT_EMAIL=\"name@example.com\"' >> config\n      # echo \"dynamop.de www.dynamp.de\" > domains.txt\n      sudo ./dehydrated --config ./config --ca http://127.0.0.1:22280 --register --accept-terms\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/disable_challengevalidation/enroll/action.yml",
    "content": "name: \"enroll_test\"\ndescription: \"enroll_test\"\ninputs:\n  TO_FAIL:\n    description: \"Enrollment is expected to fail\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Enroll lego with correct SAN\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Enroll lego incorrect SAN (to fail)\"\n    if: ${{ inputs.TO_FAIL  == 'true' }}\n    id: lego01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego-unknown.acme --http run\n    shell: bash\n\n  - name: \"Check  result \"\n    if: ${{ (inputs.TO_FAIL  == 'true' ) && (steps.lego01.outcome != 'failure') }}\n    run: |\n      echo \"acmefail outcome is ${{steps.lego01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Enroll dehydrated with incorrect SAN (to fail)\"\n    if: ${{ inputs.TO_FAIL  == 'true' }}\n    id: dehydrated01\n    continue-on-error: true\n    run: |\n      cd dehydrated/\n      sudo ./dehydrated --config ./config --ca http://127.0.0.1:22280 --validation-timeout 10 -c --domain www.dynamop.de --force\n      # sudo ./dehydrated --config ./config --ca http://127.0.0.1:22280 -c --domain www.dynamop.de --force\n    shell: bash\n\n  - name: \"Check  result \"\n    if: ${{ (inputs.TO_FAIL  == 'true' ) && (steps.dehydrated01.outcome != 'failure') }}\n    run: |\n      echo \"acmefail outcome is ${{steps.lego01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Enroll lego incorrect SAN (should not fail)\"\n    if: ${{ inputs.TO_FAIL  == 'false' }}\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv  --tls-skip-verify -a --email \"lego@example.com\" -d lego-unknown.acme --http run\n      sudo openssl x509 -in lego/certificates/lego-unknown.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Enroll dehydrated with incorrect SAN (should not fail)\"\n    if: ${{ inputs.TO_FAIL  == 'false' }}\n    run: |\n      cd dehydrated/\n      sudo ./dehydrated --config ./config --ca http://127.0.0.1:22280 --validation-timeout 10 -c --domain www.dynamop.de --force\n      sudo ./dehydrated --config ./config --ca http://127.0.0.1:22280 -c --domain www.dynamop.de --force\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile/action.yml",
    "content": "name: \"enroll_test\"\ndescription: \"enroll_test\"\ninputs:\n  TO_FAIL:\n    description: \"Enrollment is expected to fail\"\n    required: true\n    default: \"false\"\n  EAB_KID:\n    description: \"EAB KID to use for enrollment\"\n    required: false\n    default: \"\"\n  EAB_HMAC_KEY:\n    description: \"EAB HMAC key to use for enrollment\"\n    required: false\n    default: \"\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Enroll lego with correct SAN\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego.acme --eab --kid $EAB_KID --hmac $EAB_HMAC_KEY --http run\n    env:\n      EAB_KID: ${{ inputs.EAB_KID }}\n      EAB_HMAC_KEY: ${{ inputs.EAB_HMAC_KEY }}\n    shell: bash\n\n  - name: \"Enroll lego incorrect SAN (to fail)\"\n    if: ${{ inputs.TO_FAIL  == 'true' }}\n    id: lego01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego-unknown.acme --eab --kid $EAB_KID --hmac $EAB_HMAC_KEY --http run\n    env:\n      EAB_KID: ${{ inputs.EAB_KID }}\n      EAB_HMAC_KEY: ${{ inputs.EAB_HMAC_KEY }}\n    shell: bash\n\n  - name: \"Check  result \"\n    if: ${{ (inputs.TO_FAIL  == 'true' ) && (steps.lego01.outcome != 'failure') }}\n    run: |\n      echo \"acmefail outcome is ${{steps.lego01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Enroll lego incorrect SAN (should not fail)\"\n    if: ${{ inputs.TO_FAIL  == 'false' }}\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego -s https://acme-srv  --tls-skip-verify -a --email \"lego@example.com\" -d lego-unknown.acme --eab --kid $EAB_KID --hmac $EAB_HMAC_KEY --http run\n      sudo openssl x509 -in lego/certificates/lego-unknown.acme.crt -text -noout\n    env:\n      EAB_KID: ${{ inputs.EAB_KID }}\n      EAB_HMAC_KEY: ${{ inputs.EAB_HMAC_KEY }}\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/eab/enroll_unknown_credentials/action.yml",
    "content": "name: \"eab_enroll_unknown_credentials\"\ndescription: \"EAB enroll with unknown credentials\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n  EAB_KEY_ID:\n    description: \"EAB key ID\"\n    required: false\n    default: \"test-key-id\"\n  EAB_KEY_SECRET:\n    description: \"EAB key secret\"\n    required: false\n    default: \"test-key-secret\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"acme.sh - Failed registration with unknown credentials\"\n    continue-on-error: true\n    id: acmeshfail01\n    run: |\n      docker run --rm -i --network $NAME_SPACE --name=acme-sh neilpang/acme.sh:latest --register-account --server http://$ACME_SERVER:$HTTP_PORT --accountemail 'acme-sh@example.com' --eab-kid $EAB_KEY_ID --eab-hmac-key $EAB_KEY_SECRET --debug 3\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.acmeshfail01.outcome != 'failure' }}\n    run: |\n      echo \"acmeshfail outcome is ${{steps.acmeshfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"certbot - Failed registration with unknown credentials\"\n    continue-on-error: true\n    id: certbotfail01\n    run: |\n      docker run -i --rm --name certbot --network $NAME_SPACE certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://$ACME_SERVER:$HTTP_PORT --no-eff-email --eab-kid $EAB_KEY_ID --eab-hmac-key $EAB_KEY_SECRET\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.certbotfail01.outcome != 'failure' }}\n    run: |\n      echo \"certbotfail outcome is ${{steps.certbotfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"lego - Failed registration with unknown credentials\"\n    continue-on-error: true\n    id: legofail01\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --network $NAME_SPACE -p 443:443 --rm --name lego -e LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\"  -d lego-02.bar1.local --tls-skip-verify --eab --kid $EAB_KEY_ID --hmac $EAB_KEY_SECRET --tls run\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.legofail01.outcome != 'failure' }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB kid lookup failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/eab/enroll_wo_credentials/action.yml",
    "content": "name: \"eab_enroll_wo_credentials\"\ndescription: \"EAB enroll without credentials\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n  EAB_KEY_ID:\n    description: \"EAB key ID\"\n    required: false\n    default: \"test-key-id\"\n  EAB_KEY_SECRET:\n    description: \"EAB key secret\"\n    required: false\n    default: \"test-key-secret\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"acme.sh - Failed registration without credentials\"\n    continue-on-error: true\n    id: acmeshfail01\n    run: |\n      docker run --rm -i --network $NAME_SPACE --name=acme-sh neilpang/acme.sh:latest --register-account --server http://$ACME_SERVER:$HTTP_PORT --accountemail 'acme-sh@example.com' --debug 3\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"check  result \"\n    if: ${{ steps.acmeshfail01.outcome != 'failure' }}\n    run: |\n      echo \"acmeshfail outcome is ${{steps.acmeshfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"certbot - Failed registration without credentials\"\n    continue-on-error: true\n    id: certbotfail01\n    run: |\n      docker run -i --rm --name certbot --network $NAME_SPACE certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://$ACME_SERVER:$HTTP_PORT --no-eff-email\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"check  result \"\n    if: ${{ steps.certbotfail01.outcome != 'failure' }}\n    run: |\n      echo \"certbotfail outcome is ${{steps.certbotfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  # check is done within certbot logs, so no need to check again here\n  #- name: \"Check logs for registration failure\"\n  #  working-directory: examples/Docker/\n  #  run: |\n  #    if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n  #      docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n  #      sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n  #    elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n  #      docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n  #    fi\n  #  shell: bash\n  #  env:\n  #    DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"lego - Failed registration without credentials\"\n    continue-on-error: true\n    id: legofail01\n    run: |\n      docker run -i --network $NAME_SPACE -p 443:443 --rm --name lego -e LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\"  -d lego-02.bar1.local --tls-skip-verify --tls run\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"check  result \"\n    if: ${{ steps.legofail01.outcome != 'failure' }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  # check is done within lego logs, so no need to check again here\n  #- name: \"Check logs for registration failure\"\n  #  working-directory: examples/Docker/\n  #  run: |\n  #    if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n  #      docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n  #      sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n  #    elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n  #      docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:externalAccountRequired', 'detail': 'External account binding required'}\"\n  #    fi\n  #  shell: bash\n  #  env:\n  #    DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/eab/enroll_wrong_credentials/action.yml",
    "content": "name: \"eab_enroll_wrong_credentials\"\ndescription: \"EAB enroll with wrong credentials\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n  EAB_KEY_ID:\n    description: \"EAB key ID\"\n    required: false\n    default: \"test-key-id\"\n  EAB_KEY_SECRET:\n    description: \"EAB key secret\"\n    required: false\n    default: \"test-key-secret\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"acme.sh - Failed registration with wrong credentials\"\n    continue-on-error: true\n    id: acmeshfail01\n    run: |\n      docker run --rm -i --network $NAME_SPACE --name=acme-sh neilpang/acme.sh:latest --register-account --server http://$ACME_SERVER:$HTTP_PORT --accountemail 'acme-sh@example.com' --eab-kid $EAB_KEY_ID --eab-hmac-key $EAB_KEY_SECRET --debug 3\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.acmeshfail01.outcome != 'failure' }}\n    run: |\n      echo \"acmeshfail outcome is ${{steps.acmeshfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"certbot - Failed registration with wrong credentials\"\n    continue-on-error: true\n    id: certbotfail01\n    run: |\n      docker run -i --rm --name certbot --network $NAME_SPACE certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://$ACME_SERVER:$HTTP_PORT --no-eff-email --eab-kid $EAB_KEY_ID --eab-hmac-key $EAB_KEY_SECRET\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.certbotfail01.outcome != 'failure' }}\n    run: |\n      echo \"certbotfail outcome is ${{steps.certbotfail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"lego - Failed registration with wrong credentials\"\n    continue-on-error: true\n    id: legofail01\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --network $NAME_SPACE -p 443:443 --rm --name lego -e LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true goacme/lego -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\"  -d lego-02.bar1.local --tls-skip-verify --eab --kid $EAB_KEY_ID --hmac $EAB_KEY_SECRET --tls run\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      EAB_KEY_ID: ${{ inputs.EAB_KEY_ID }}\n      EAB_KEY_SECRET: ${{ inputs.EAB_KEY_SECRET }}\n\n  - name: \"check  result \"\n    if: ${{ steps.legofail01.outcome != 'failure' }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Check logs for registration failure\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:unauthorized', 'detail': 'EAB signature verification failed'}\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ejbca_ca_handler/ejbca_prep/action.yml",
    "content": "name: \"ejbca_prep\"\ndescription: \"ejbca_prep\"\ninputs:\n  RUNNER_IP:\n    description: \"Runner IP\"\n    required: true\n  WORKING_DIR:\n    description: \"Working directory\"\n    required: true\n    default: ${{ github.workspace }}\noutputs:\n  SAEC:\n    description: \"Superadmin password\"\n    value: ${{ env.SAEC }}\n  CAID:\n    description: \"CAID of acmeca\"\n    value: ${{ env.CAID }}\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Prepare Environment\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      mkdir -p data/acme_ca\n      sudo chmod -R 777 data/acme_ca\n      sudo sh -c \"echo '$EJBCA_IP ejbca' >> /etc/hosts\"\n    env:\n      EJBCA_IP: ${{ inputs.RUNNER_IP }}\n    shell: bash\n\n  - name: \"Instanciate ejbca server\"\n    run: |\n      docker run -id --rm -p 80:8080 -p 443:8443 -e TLS_SETUP_ENABLED=true -v $(pwd)/examples/ejbca:/tmp/data  -v \"$WORKING_DIR/data\":/tmp/store --name \"ejbca\" -h ejbca keyfactor/ejbca-ce\n    shell: bash\n    env:\n      WORKING_DIR: ${{ inputs.WORKING_DIR }}\n\n  - name: \"Sleep for 180s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 180s\n\n  - name: \"Get randmonly generated Superadmin password for ejbca instance\"\n    run: |\n      echo SAEC=$(docker logs ejbca | grep /opt/keyfactor/bin/start.sh | grep Password: | awk -F'Password: ' '{print $2}' | awk -F ' ' '{print $1}') >> $GITHUB_ENV\n    shell: bash\n\n  - run: echo \"Randmonly generated Superadmin password is ${{ env.SAEC }}\"\n    shell: bash\n\n  - run: sudo echo \"$SAEC\" > \"$WORKING_DIR/data/passphrase.txt\"\n    shell: bash\n    env:\n      SAEC: ${{ env.SAEC }}\n      WORKING_DIR: ${{ inputs.WORKING_DIR }}\n\n  - name: \"Configure ejbca\"\n    run: |\n      docker exec -i ejbca bin/ejbca.sh ca getcacert --caname ManagementCA -f /tmp/store/acme_ca/ca_bundle.pem\n      docker exec -i ejbca bin/ejbca.sh config protocols enable --name \"REST Certificate Management\"\n      docker exec -i ejbca bin/ejbca.sh config protocols enable --name \"REST Certificate Management V2\"\n      docker exec -i ejbca bin/ejbca.sh ca init acmeca \"CN=acmeca\" soft foo123 4096 RSA -v 365 --policy 2.5.29.32.0 -s SHA256WithRSA\n    shell: bash\n\n  - name: \"Get CAID\"\n    run: |\n      echo CAID=$(docker logs ejbca | grep \"msg=CA with id\" | grep \"and name acmeca added\" | awk -F'with id ' '{print $2}' | awk -F' and name' '{print $1}') >> $GITHUB_ENV\n    shell: bash\n\n  - run: echo \"CAID of acmeca is ${{ env.CAID }}\"\n    shell: bash\n\n  - name: \"Create subca\"\n    run: |\n      docker exec -i ejbca bin/ejbca.sh ca init acmesubca \"CN=acmesubca\" soft foo123 4096 RSA -v 365 --policy 2.5.29.32.0 -s SHA256WithRSA --signedby $CAID\n      docker exec -i ejbca bin/ejbca.sh ca importprofiles -d /tmp/data/\n    env:\n      CAID: ${{ env.CAID }}\n    shell: bash\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Fetch superadmin certificate and key\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      docker exec -i ejbca bin/ejbca.sh ra setendentitystatus superadmin 10\n      docker exec -i ejbca bin/ejbca.sh ra setclearpwd superadmin $SAEC\n      docker exec -i ejbca bin/ejbca.sh batch\n      docker cp ejbca:/opt/keyfactor/p12/superadmin.p12 data/acme_ca/\n      mkdir -p data/volume/acme_ca\n      cp data/acme_ca/superadmin.p12 data/volume/acme_ca/\n      cp data/acme_ca/ca_bundle.pem data/volume/acme_ca/\n    env:\n      SAEC: ${{ env.SAEC }}\n    shell: bash\n\n  - name: \"Test superadmin  certificate and key\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      curl https://127.0.0.1/ejbca/ejbca-rest-api/v1/certificate/status --cert-type P12 --cert data/acme_ca/superadmin.p12:$SAEC --insecure\n      curl https://ejbca/ejbca/ejbca-rest-api/v1/certificate/status --cert-type P12 --cert data/acme_ca/superadmin.p12:$SAEC --cacert data/acme_ca/ca_bundle.pem\n    env:\n      SAEC: ${{ env.SAEC }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/ejbca_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01a - enrollment without profile (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n\n  - name: \"ACME Profile - 02 - Allowed profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile acmeca2\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout  | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"ACME Profile - 03 - enrollment with unknown profile (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: ACME Profile 03 - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/ejbca_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB wit headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01a - enrollment without profile (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01b - enrollment with profile (pick value from list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile acmeca1\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01c - enrollment with profile containing value not included in list (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile acmeca3\n    shell: bash\n\n  - name: EAB ACME Profile 01c - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01d - enrollment with profile containing parameter not in json (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run --profile acmeca2\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout  | grep -i acmeca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 02 -  profiling ca and cert_profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmeca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 02 -  revoke profiled ca\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg revoke\n    shell: bash\n\n  - name: \"EAB ACME Profile - 03 - domainlist validation fails (to fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n\n  - name: \"EAB ACME Profile - 03 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profile - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/ejbca_ca_handler/enroll_eab_w_headerinfo/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB wit headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01a - enrollment without header-info field (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01b - enrollment with header-info field (pick value from list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent cert_profile_name=acmeca1 -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01c - enrollment with header-info field containing value not included in list (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent cert_profile_name=acmeca3 -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n    shell: bash\n\n  - name: EAB with headerinfo 01c - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01d - enrollment with header-info field cotaining an invalid parameter (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent ca_name=foo -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 01e - enrollment with header-info field containing parameter not in json (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent cert_profile_name=acmeca2 -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout  | grep -i acmeca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 02 -  profilinging ca and cert_profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmeca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB with headerinfo - 03 - domainlist validation fails (to fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n\n  - name: EAB with headerinfo - 03 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB with headerinfo - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/ejbca_ca_handler/enroll_eab_wo_headerinfo/action.yml",
    "content": "name: \"enroll_wo_headerinfo\"\ndescription: \"enroll_wo_headerinfo\"\n\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB without headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB without headerinfo - 01a - enrollment without header-info field (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 01b - enrollment with header-info field included in list (silent ignore)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent cert_profile_name=acmeca1 -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 01c - with header-info field containing value not included in list (silent ignore)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent cert_profile_name=acmeca3 -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 02 -  profilinging ca and cert_profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmeca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB without headerinfo - 03 - domainlist validation fails (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n\n  - name: EAB without headerinfo - 03 - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB without headerinfo - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i acmesubca\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll/action.yml",
    "content": "name: \"acme_email_enroll\"\ndescription: \"acme_email_enroll\"\ninputs:\n  TO_FAIL:\n    description: \"Enrollment is expected to fail\"\n    required: true\n    default: \"false\"\n  INSTALL:\n    description: \"Install acme_email and dependencies\"\n    required: true\n    default: \"false\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test if http://127.0.0.1:22280/directory is accessible\"\n    run: curl --insecure -f http://127.0.0.1:22280/directory\n    shell: bash\n\n  - name: \"Install acme_email and dependencies\"\n    if: ${{ inputs.INSTALL  == 'true' }}\n    run: |\n      echo \"### Install acme-email\"\n      git clone https://github.com/grindsa/acme_email.git\n      cd acme_email\n      python3 -m venv venv\n      source venv/bin/activate\n      pip3 install .\n    shell: bash\n\n  - name: \"Enroll certificate\"\n    run: |\n      echo \"### Enroll certificate\"\n      cd acme_email\n      source venv/bin/activate\n      python3 cli.py cert --server http://127.0.0.1:22280 --no-verify-ssl --config-dir . --work-dir . --logs-dir . -e jum@mailserver.acme --contact joern.mewes@gmx.net --imap --login jum@mailserver.acme --password jumstarter --host 127.0.0.1 --ssl --smtp-method STARTTLS --smtp-port 587 --smtp-host 127.0.0.1 --no-passphrase\n    shell: bash\n\n  - name: \"Enroll certificate with wrong email (to fail)\"\n    if: ${{ inputs.TO_FAIL  == 'true' }}\n    id: certbot01\n    continue-on-error: true\n    run: |\n      echo \"### Enroll certificate\"\n      cd acme_email\n      sudo rm -rf ./live/*\n      source venv/bin/activate\n      python3 cli.py cert --server http://127.0.0.1:22280 --no-verify-ssl --config-dir . --work-dir . --logs-dir . -e ulme@mailserver.acme --contact joern.mewes@gmx.net --imap --login jum@mailserver.acme --password jumstarter --host 127.0.0.1 --ssl --smtp-method STARTTLS --smtp-port 587 --smtp-host 127.0.0.1 --no-passphrase\n    shell: bash\n\n  - name: \"Check  result \"\n    if: ${{ (inputs.TO_FAIL  == 'true' ) && (steps.certbot01.outcome != 'failure') }}\n    run: |\n      echo \"acmefail outcome is ${{steps.certbot01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Enroll certificate with wrong email (not to fail)\"\n    if: ${{ inputs.TO_FAIL  == 'false' }}\n    run: |\n      echo \"### Enroll certificate\"\n      cd acme_email\n      sudo rm -rf ./live/*\n      source venv/bin/activate\n      python3 cli.py cert --server http://127.0.0.1:22280 --no-verify-ssl --config-dir . --work-dir . --logs-dir . -e ulme@mailserver.acme --contact joern.mewes@gmx.net --imap --login jum@mailserver.acme --password jumstarter --host 127.0.0.1 --ssl --smtp-method STARTTLS --smtp-port 587 --smtp-host 127.0.0.1 --no-passphrase\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/enrollment_timeout/enroll/action.yml",
    "content": "name: \"enroll timeout\"\ndescription: test enrollment timeout handling for various ACME clients\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"container\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme  --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n    shell: bash\n\n  - name: \"Check timeout\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"Certificate.process_certificate_enrollment_request() ended with: timeout:Enrollment process timed out\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec acme-srv grep \"Certificate.process_certificate_enrollment_request() ended with: timeout:Enrollment process timed out\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme  --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n    shell: bash\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Check certificate reusage\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"Reuse existing certificate\"\n        docker compose logs | grep \"issued for account\" | grep \"Serial:\" | grep \"reused: True\"\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec acme-srv grep \"Reuse existing certificate\" /var/log/messages\n        docker exec -i acme-srv bash -c 'tail -n 500 /var/log/messages | grep \"issued for account\" | grep \"Serial:\" | grep \"reused: True\"'\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"Enroll Lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme  --cert.timeout 180 --http run\n    shell: bash\n\n  - name: \"Check timeout\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"Certificate.process_certificate_enrollment_request() ended with: timeout:Enrollment process timed out\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"Register certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Enroll certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot --issuance-timeout 180\n    shell: bash\n\n  - name: \"Check timeout\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"Certificate.process_certificate_enrollment_request() ended with: timeout:Enrollment process timed out\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/entrust_ca_handler/enroll/action.yml",
    "content": "name: \"acme_clients - enroll, renew and revoke certificates\"\ndescription: \"Test if acme.sh, certbot and lego can enroll, renew and certificates\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  REVOCATION:\n    description: \"Revocation method\"\n    required: true\n    default: \"true\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        sudo mkdir -p certbot/\n        sudo mkdir -p lego/ca\n        sudo cp .github/acme2certifier_cabundle.pem certbot/\n        sudo cp .github/acme2certifier_cabundle.pem lego/\n        if [ -f cert-2.pem ]; then\n          echo \"delete cert-2.pem\"\n          rm -f cert-2.pem\n        fi\n        if [ -f cert-1.pem ]; then\n          echo \"delete cert-1.pem\"\n          rm -f cert-1.pem\n        fi\n        ls -la\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Enroll lego\"\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTP_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTP_PORT -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Revoke lego\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"#### HTTP - Revoke lego\"\n        docker run -i -v $PWD/lego:/.lego/ --rm --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTP_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE revoke\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll acme.sh\"\n      run: |\n        echo \"##### HTTPS - Enroll acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --debug 1 --output-insecure --insecure\n          ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --keylength 2048 --debug 1 --output-insecure --insecure\n        fi\n\n        awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/ca.cer\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            echo \"Multiple CA certs\"\n            openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          else\n            echo \"Single Root ca\"\n            openssl verify -CAfile cert-1.pem acme-sh/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE${ECC}/acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE.cer\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke HTTP-01 single domain acme.sh\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"##### HTTPS - Revoke HTTP-01 single domain acme.sh #####\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --revoke --server https://$ACME_SERVER:$HTTPS_PORT --revoke -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --standalone --debug 2 --output-insecure  --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Decativate acme.sh #####\"\n      run: |\n        echo \"##### HTTPS - Decativate acme.sh\"\n        docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --name acme-sh$HOSTNAME_SUFFIX --network $NAME_SPACE neilpang/acme.sh:latest --deactivate-account --server https://$ACME_SERVER:$HTTPS_PORT --debug 2 --output-insecure --insecure\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll certbot\"\n      run: |\n        echo \"##### HTTPS - Enroll certbot #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        fi\n\n        if [ \"$VERIFY_CERT\" == \"true\" ]; then\n          if [ -f cert-2.pem ]; then\n            sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n          else\n            echo \"single root ca\"\n            sudo openssl verify -CAfile cert-1.pem certbot/live/certbot/cert.pem\n          fi\n        fi\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Revoke certbot\"\n      if: ${{ inputs.REVOCATION == 'true' }}\n      run: |\n        echo \"##### HTTPS - Revoke certbot #####\"\n        docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --server https://$ACME_SERVER:$HTTPS_PORT --no-verify-ssl --delete-after-revoke --cert-name certbot\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTP - Enroll lego with wrong domain - should fail\"\n      id: legofail01\n      continue-on-error: true\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTP_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.acme --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTP_PORT -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.acme --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check  result \"\n      if: steps.legofail01.outcome != 'failure'\n      run: |\n        echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Delete acme-sh, letsencypt and lego folders\"\n      run: |\n        sudo rm -rf  lego/*\n        sudo rm -rf  acme-sh/*\n        sudo rm -rf  certbot/*\n      shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/entrust_ca_handler/enroll_eab/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network rm-rf.ninja curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network rm-rf.ninja curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network rm-rf.ninja goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --key-type=rsa2048 --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.rm-rf.ninja --http run\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network rm-rf.ninja goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.rm-rf.ninja revoke\n    shell: bash\n\n  - name: \"EAB - 02 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network rm-rf.ninja goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.rm-rf.ninja --http run\n    shell: bash\n\n  - name: \"EAB - 04a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/error_tests/account_checks/action.yml",
    "content": "name: \"account_error_checking\"\ndescription: \"EAB enroll with unknown credentials\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n  ALMA_START:\n    description: \"Start alma container\"\n    required: true\n    default: \"true\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Test Registration without email\"\n      working-directory: acmeshell\n      run: |\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo.bar.local,\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command to trigger error message\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for invalidContact error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:malformed\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for malformed error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"{'status': 400, 'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'Contact information is missing'}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 400, 'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'Contact information is missing'}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test Registration with wrong email format\"\n      working-directory: acmeshell\n      run: |\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo.bar.local,\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command to trigger error message\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for invalidContact error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:invalidContact\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for invalidContact error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"{'status': 400, 'type': 'urn:ietf:params:acme:error:invalidContact', 'detail': 'The provided contact URI was invalid: Invalid contact information'}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 400, 'type': 'urn:ietf:params:acme:error:invalidContact', 'detail': 'The provided contact URI was invalid: Invalid contact information'}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test Registration with correct email format\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        getAccount\n        post -body='{\"status\":\"deactivated\"}' {{ account }}\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log\"\n      working-directory: acmeshell\n      run: |\n        # cat acmeshell.enroll.log\n        grep \"{\\\"status\\\": \\\"valid\\\"\" acmeshell.enroll.log\n        grep \"{\\\"status\\\": \\\"deactivated\\\"}\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"Account.onlyreturnexisting() ended with: 200\"\n          docker compose logs | grep \"Account._deactivate_account(\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Account.onlyreturnexisting() ended with: 200\"\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Account._deactivate_account(\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test registration without TOC agreement using a modifed acme.sh\"\n      continue-on-error: true\n      id: acmeshfail01\n      run: |\n        docker exec alma bash -c 'grep regjson /root/.acme.sh/acme.sh'\n        docker exec alma sed -i 's/termsOfServiceAgreed/foo/g' /root/.acme.sh/acme.sh\n        docker exec alma bash -c 'grep regjson /root/.acme.sh/acme.sh'\n        docker exec alma bash -c '/root/.acme.sh/acme.sh --register-account --server http://acme-srv --accountemail acme-sh@example.com'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"check  result \"\n      if: ${{ steps.acmeshfail01.outcome != 'failure' }}\n      run: |\n        echo \"acmeshfail outcome is ${{steps.acmeshfail01.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"no tos statement found.\"\n          docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:userActionRequired', 'detail': 'termsofserviceagreed flag missing'}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"no tos statement found.\"\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:userActionRequired', 'detail': 'termsofserviceagreed flag missing'}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test registration with TOC agreement false using a modifed acme.sh\"\n      id: acmeshfail02\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c 'cp /root/.acme.sh/acme.sh.bup /root/.acme.sh/acme.sh'\n        docker exec alma bash -c 'grep regjson /root/.acme.sh/acme.sh'\n        docker exec alma sed -i 's/\\\\\"termsOfServiceAgreed\\\\\": true/\\\\\"termsOfServiceAgreed\\\\\": false/g' /root/.acme.sh/acme.sh\n        docker exec alma bash -c 'grep regjson /root/.acme.sh/acme.sh'\n        docker exec alma bash -c '/root/.acme.sh/acme.sh --register-account --server http://acme-srv --accountemail acme-sh@example.com'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n\n    - name: \"check  result \"\n      if: ${{ steps.acmeshfail02.outcome != 'failure' }}\n      run: |\n        echo \"acmeshfail outcome is ${{steps.acmeshfail02.outcome }}\"\n        exit 1\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"tos:False\"\n          docker compose logs | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:userActionRequired', 'detail': 'Terms of service must be agreed'}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"tos:False\"\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"{'status': 403, 'type': 'urn:ietf:params:acme:error:userActionRequired', 'detail': 'Terms of service must be agreed'}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n\n    - name: \"Update account information\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        post -body='{\"contact\":[\"mailto:new@example.com\"]}' {{ account }}\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log\"\n      working-directory: acmeshell\n      run: |\n        # grep \"HTTP\\/1.1 201 OK\" acmeshell.enroll.log\n        # grep \"HTTP\\/1.1 200 Created\" acmeshell.enroll.log\n        grep \"\\\"contact\\\": \\[\\\"mailto:new@example.com\\\"\\], \\\"createdAt\\\":\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"validate: new@example.com result: True\"\n          docker compose logs | grep \"DBStore.account_update(\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"validate: new@example.com result: True\"\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"DBStore.account_update(\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Unknown account request\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        post -body='{\"foo\": \"bar\"}' {{ account }}\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log\"\n      working-directory: acmeshell\n      run: |\n        grep \"{\\\"status\\\": 400, \\\"type\\\": \\\"urn:ietf:params:acme:error:malformed\\\", \\\"detail\\\": \\\"Unknown request\\\"}\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"Error.acme_errormessage(urn:ietf:params:acme:error:malformed)\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Error.acme_errormessage(urn:ietf:params:acme:error:malformed)\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"wrong status update\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        post -body='{\"status\":\"valid\"}' {{ account }}\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log\"\n      working-directory: acmeshell\n      run: |\n        grep \"{\\\"status\\\": 400, \\\"type\\\": \\\"urn:ietf:params:acme:error:malformed\\\", \\\"detail\\\": \\\"Invalid status for deactivation\\\"}\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"Error.acme_errormessage(urn:ietf:params:acme:error:malformed)\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Error.acme_errormessage(urn:ietf:params:acme:error:malformed)\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Key Rollover\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        newKey -id=replacement.account.key\n        keyRollover -keyID=replacement.account.key\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell newAccount command\"\n      working-directory: acmeshell\n      continue-on-error: true\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -autoregister=false --account=\"\" -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log\"\n      working-directory: acmeshell\n      run: |\n        grep \"Rollover for\" acmeshell.enroll.log | grep \"completed\"\n      shell: bash\n\n    - name: \"Check a2c logs\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep \"Account._rollover_account_key(\"\n          docker compose logs | grep \"Account._validate_key_change(\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Account._rollover_account_key(\"\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"Account._validate_key_change(\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/error_tests/acmeshell_install/action.yml",
    "content": "name: \"Install acmeshell\"\ndescription: \"Install acmeshell\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  ALMA_START:\n    description: \"Start alma container\"\n    required: true\n    default: \"true\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acmeshell/\n      shell: bash\n\n    - name: \"Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Install acmeshell\"\n      if: ${{ inputs.ALMA_START == 'true' }}\n      run: |\n        wget -c https://github.com/cpu/acmeshell/releases/download/v0.0.2-rc4/acmeshell_0.0.2-rc4_Linux_x86_64.tar.gz -O - | tar -xz\n        mv acmeshell_0.0.2-rc4_Linux_x86_64/acmeshell acmeshell/\n        chmod +x acmeshell/acmeshell\n        # ls -la acmeshell/\n      shell: bash\n      env:\n        VERIFY_CERT: ${{ inputs.VERIFY_CERT }}\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Run alma container\"\n      if: ${{ inputs.ALMA_START == 'true' }}\n      run: |\n        docker run -id --name alma --network $NAME_SPACE -v $(pwd)/acmeshell:/acmeshell almalinux/9-minimal\n        sleep 5\n        docker ps\n        # install acme.sh for additional tests\n        docker exec alma bash -c 'microdnf install -y tar gzip openssl'\n        docker exec alma bash -c 'curl https://get.acme.sh | sh -s email=grindsa@github.com --force'\n        docker exec alma bash -c 'cp /root/.acme.sh/acme.sh /root/.acme.sh/acme.sh.bup'\n        docker exec alma bash -c 'curl -f http://acme-srv/directory'\n      shell: bash\n      env:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/error_tests/order_checks/action.yml",
    "content": "name: \"order_error_checking\"\ndescription: \"EAB enroll with unknown credentials\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n  ALMA_START:\n    description: \"Start alma container\"\n    required: true\n    default: \"true\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: \"Test identifier limit\"\n      working-directory: acmeshell\n      run: |\n        cat <<EOT > commands.shell\n        newAccount -contacts=foo@bar.local,\n        newOrder -identifiers=foo-01.bar.local,foo-02.bar.local,foo-03.bar.local\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for rejectedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:rejectedIdentifier\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for rejectedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier limit exceeded['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier limit exceeded['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test wrong identifier format for type DNS\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newOrder -identifiers=foo@bar.local\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for rejectedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:rejectedIdentifier\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for rejectedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier value foo@bar.local not allowed['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier value foo@bar.local not allowed['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test wrong identifier format for type IP\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        post -body='{\"identifiers\":[{\"type\":\"ip\", \"value\":\"unknown.bar.local\"}]}' newOrder\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for rejectedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:rejectedIdentifier\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for rejectedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier value unknown.bar.local not allowed['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]identifier value unknown.bar.local not allowed['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test unknown identifier type\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        post -body='{\"identifiers\":[{\"type\":\"unknown\", \"value\":\"unknown.bar.local\"}]}' newOrder\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for rejectedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:unsupportedIdentifier\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for unsupportedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:unsupportedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Could not process order['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:unsupportedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Could not process order['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test missing identifier type\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        post -body='{\"identifiers\":[{\"foo\":\"unknown\", \"value\":\"unknown.bar.local\"}]}' newOrder\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for malformedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:malformed\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for malformedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:malformed['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Identifier type is missing['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:malformed['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Identifier type is missing['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test missing identifier value\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        post -body='{\"identifiers\":[{\"type\":\"dns\", \"foo\":\"unknown.bar.local\"}]}' newOrder\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for malformedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        grep \"urn:ietf:params:acme:error:malformed\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for malformedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:malformed['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Identifier value is missing['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 400, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:malformed['\\\"], ['\\\"]detail['\\\"]: ['\\\"]Identifier value is missing['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n    - name: \"Test ADL feature\"\n      working-directory: acmeshell\n      run: |\n        rm -f acmeshell.enroll.log\n        cat <<EOT > commands.shell\n        newOrder -identifiers=foo-01.bar1.local\n        EOT\n      shell: bash\n\n    - name: \"Run acmeshell NewOrder command to trigger error message\"\n      working-directory: acmeshell\n      run: |\n        docker exec alma bash -c '/acmeshell/acmeshell -directory http://acme-srv -postAsGet=true -printResponses -printRequests -contact=grindsa@foo.bar -in /acmeshell/commands.shell &> /acmeshell/acmeshell.enroll.log'\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Check acmeshell log for rejectedIdentifier error\"\n      working-directory: acmeshell\n      run: |\n        cat acmeshell.enroll.log\n        grep \"urn:ietf:params:acme:error:rejectedIdentifier\" acmeshell.enroll.log\n      shell: bash\n\n    - name: \"Check a2c logs for rejectedIdentifier error\"\n      working-directory: examples/Docker/\n      run: |\n        if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n          docker compose logs | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]FQDN/SAN foo-01.bar1.local not allowed by configuration['\\\"])}\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n        elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n          docker exec -i acme-srv tail -n 500 /var/log/messages | grep -E \"\\{(['\\\"]status['\\\"]: 403, ['\\\"]type['\\\"]: ['\\\"]urn:ietf:params:acme:error:rejectedIdentifier['\\\"], ['\\\"]detail['\\\"]: ['\\\"]FQDN/SAN foo-01.bar1.local not allowed by configuration['\\\"])}\"\n        fi\n      shell: bash\n      env:\n        DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/harica/acme_enroll/action.yml",
    "content": "name: \"enroll_acmeprofile\"\ndescription: \"enroll_acmeprofile‚\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix for the domain\"\n    required: true\n    default: \"-unknown\"\n  CERT_TIMEOUT:\n    description: \"Certificate timeout\"\n    required: true\n    default: \"60\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Enroll lego - might fail\"\n    continue-on-error: true\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$DOMAIN --cert.timeout $CERT_TIMEOUT --http run\n    shell: bash\n    env:\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n  - name: \"Sleep for 15s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 15s\n\n  - name: \"Enroll lego\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$DOMAIN --cert.timeout $CERT_TIMEOUT --http run\n      # sudo chmod -R a+rw lego\n      # sudo cp lego/certificates/lego$HOSTNAME_SUFFIX.$DOMAIN.crt lego/certificates/lego$HOSTNAME_SUFFIX.$DOMAIN.issuer.crt ./\n      # sudo awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < lego$HOSTNAME_SUFFIX.$DOMAIN.issuer.crt\n      # sudo openssl x509 -in lego$HOSTNAME_SUFFIX.$DOMAIN.crt -text -noout\n      # sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego$HOSTNAME_SUFFIX.$DOMAIN.crt\n    shell: bash\n    env:\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n\n  - name: \"Enroll Certbot\"\n    run: |\n      docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --standalone --preferred-challenges http --server  https://acme-srv --email grindsa@foo.bar --agree-tos --no-eff-email --no-verify-ssl -d \"certbot$HOSTNAME_SUFFIX.acme.dynamop.de\" --issuance-timeout $CERT_TIMEOUT\n    shell: bash\n    env:\n      HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n      CERT_TIMEOUT: ${{ inputs.CERT_TIMEOUT }}\n"
  },
  {
    "path": ".github/actions/wf_specific/hooks/enroll/action.yml",
    "content": "name: \"acme_email_enroll\"\ndescription: \"acme_email_enroll\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: false\n    default: \"container\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test if http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n    shell: bash\n    env:\n      ACME_SERVER: ${{ inputs.ACME_SERVER }}\n      HTTP_PORT: ${{ inputs.HTTP_PORT }}\n      HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Successful Enrollment triggering email success hook\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n    shell: bash\n\n  - name: \"Sleep for 15s to allow email to be sent\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 15s\n\n  - name: \"Check logs for success hook\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"Email notification sent successfully to\"\n        docker compose logs | grep \"\\[ACME\\] acme2certifier success\"\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"acme2certifier success\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"Failed Enrollment triggering email post_hook hook\"\n    continue-on-error: true\n    id: legofail01\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.unknown --http run\n    shell: bash\n\n  - name: \"check  result \"\n    if: ${{ steps.legofail01.outcome != 'failure' }}\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Check logs for post_hook (failed)\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"urn:ietf:params:acme:error:rejectedIdentifier\"\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"urn:ietf:params:acme:error:rejectedIdentifier\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/manual/setup/action.yml",
    "content": "name: \"acme_email_enroll\"\ndescription: \"acme_email_enroll\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: false\n    default: \"acme\"\n  DB_HANDLER:\n    description: \"Database handler type\"\n    required: false\n    default: \"wsgi\"\n  ACME_SERVER:\n    description: \"ACME server hostname\"\n    required: false\n    default: \"acme-srv\"\n  HTTP_PORT:\n    description: \"ACME server HTTP port\"\n    required: false\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"ACME server HTTPS port\"\n    required: false\n    default: \"443\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Generate keys and certificates\"\n    uses: ./.github/actions/cert_gen\n    with:\n      DESTINATION_PATH: \".github\"\n      EE_KEY: \"acme2certifier_key.pem\"\n      EE_CERT: \"acme2certifier_cert.pem\"\n      EE_CSR: \"acme2certifier_csr.pem\"\n      EE_BUNDLE: \"acme2certifier.pem\"\n      CA_BUNDLE: \"acme2certifier_cabundle.pem\"\n      ISSUING_CA_KEY: \"test/ca/sub-ca-key.pem\"\n      ISSUING_CA_CERT: \"test/ca/sub-ca-cert.pem\"\n      ISSUING_CA_PASSPHRASE: \"Test1234\"\n      ROOT_CA_CERT: \"test/ca/root-ca-cert.pem\"\n\n  - name: \"Instanciate Mariadb\"\n    if: inputs.DB_HANDLER == 'django'\n    uses: ./.github/actions/mariadb_prep\n    with:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Setup environment for ubuntu 24.04\"\n    run: |\n      docker run -d --rm --name acme-srv --network acme --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v $PWD:/tmp/acme2certifier jrei/systemd-ubuntu:24.04\n      docker exec acme-srv apt-get update  # && apt-get upgrade\n      docker exec acme-srv apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 curl krb5-user libkrb5-3 python3-gssapi rsyslog\n      docker exec acme-srv systemctl enable rsyslog\n      docker exec acme-srv systemctl start rsyslog\n    shell: bash\n\n  - name: \"Setup environment for ubuntu 24.04\"\n    run: |\n      docker exec acme-srv bash -c 'cd /tmp/acme2certifier && pip3 install Cython --break-system-packages && python3 setup.py install'\n      docker exec acme-srv cp /var/lib/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\n      docker exec acme-srv cp /var/lib/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n      docker exec acme-srv rm /etc/nginx/sites-enabled/default\n      docker exec acme-srv ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n      docker exec acme-srv ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n      docker exec acme-srv cp /tmp/acme2certifier/.github/acme2certifier_cert.pem /etc/ssl/certs/acme2certifier_cert.pem\n      docker exec acme-srv cp /tmp/acme2certifier/.github/acme2certifier_key.pem /etc/ssl/private/acme2certifier_key.pem\n      docker exec acme-srv cp /var/lib/acme2certifier/examples/nginx/acme2certifier.ini /var/lib/acme2certifier\n      # configure cahandler\n      docker exec acme-srv cp /var/lib/acme2certifier/examples/acme2certifier_wsgi.py  /var/lib/acme2certifier\n      docker exec acme-srv mkdir -p /var/lib/acme2certifier/volume/acme_ca/certs\n      docker exec acme-srv cp /tmp/acme2certifier/test/ca/sub-ca-key.pem /tmp/acme2certifier/test/ca/sub-ca-crl.pem /tmp/acme2certifier/test/ca/sub-ca-cert.pem /tmp/acme2certifier/test/ca/root-ca-cert.pem /var/lib/acme2certifier/volume/acme_ca/\n      docker exec acme-srv cp /tmp/acme2certifier/.github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /var/lib/acme2certifier/volume/acme_srv.cfg\n      docker exec acme-srv ln -s /var/lib/acme2certifier/volume/acme_srv.cfg /var/lib/acme2certifier/acme_srv/\n\n      # copy db handler\n      docker exec acme-srv cp /var/lib/acme2certifier/examples/db_handler/${DB_HANDLER}_handler.py /var/lib/acme2certifier/acme_srv/db_handler.py\n    shell: bash\n    env:\n      DB_HANDLER: ${{ inputs.DB_HANDLER }}\n      # DB_HANDLER: 'wsgi' # Force wsgi for now, as django handler is not working yet\n\n  - name: \"Configure django\"\n    if: inputs.DB_HANDLER == 'django'\n    run: |\n      # delete wsgi handler as it is not needed and cause confusion with django handler\n      docker exec acme-srv rm /var/lib/acme2certifier/acme2certifier_wsgi.py\n      docker exec acme-srv apt-get install -y python3-django python3-mysqldb python3-pymysql python3-yaml\n      docker exec acme-srv bash -c 'cp -R /var/lib/acme2certifier/examples/django/* /var/lib/acme2certifier/'\n      docker exec acme-srv bash -c 'sed -i \"s/acme2certifier_wsgi/acme2certifier.wsgi/g\" /var/lib/acme2certifier/acme2certifier.ini'\n      docker exec acme-srv cp /tmp/acme2certifier/.github/django_settings_mariadb.py /var/lib/acme2certifier/acme2certifier/settings.py\n      docker exec acme-srv bash -c 'cd /var/lib/acme2certifier && python3 manage.py makemigrations'\n      docker exec acme-srv bash -c 'cd /var/lib/acme2certifier && python3 manage.py migrate && python3 manage.py loaddata acme_srv/fixture/status.yaml'\n      docker exec acme-srv chown -R www-data:www-data /var/lib/acme2certifier/\n    shell: bash\n\n  - name: \"Configure and start services\"\n    run: |\n\n      docker exec acme-srv bash -c \"cat <<EOT > /etc/systemd/system/acme2certifier.service\n      [Unit]\n      Description=uWSGI instance to serve acme2certifier\n      After=network.target\n\n      [Service]\n      User=www-data\n      Group=www-data\n      WorkingDirectory=/var/lib/acme2certifier\n      Environment=\\\"PATH=/var/lib/acme2certifier\\\"\n      ExecStart=uwsgi --ini acme2certifier.ini\n\n      [Install]\n      WantedBy=multi-user.target\n      EOT\"\n\n      docker exec acme-srv chown -R www-data:www-data /var/lib/acme2certifier/\n      # docker exec acme-srv chmod a+x /var/lib/acme2certifier/acme_srv\n      docker exec acme-srv systemctl start acme2certifier\n      docker exec acme-srv systemctl enable acme2certifier\n      docker exec acme-srv systemctl restart nginx\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_acmeprofile\"\ndescription: \"Enroll an ACME profile\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n  TAIL_NUMBER:\n    description: \"Number of lines to tail\"\n    required: false\n    default: \"500\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network \"$NAME_SPACE\" curlimages/curl -f http://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network \"$NAME_SPACE\" curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"ACME Profile - Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 01 Enroll lego with template in acme_srv.cfg (WebServer)\"\n    run: |\n      sudo rm -rf lego/\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network \"$NAME_SPACE\" goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d \"lego.$NAME_SPACE\" --http run\n      sudo openssl verify -CAfile cert-1.pem \"lego/certificates/lego.$NAME_SPACE.crt\"\n      sudo openssl x509 -in \"lego/certificates/lego.$NAME_SPACE.crt\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"ACME Profile - 01 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServer\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServer\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n\n  - name: \"ACME Profile - 02 - Enroll lego with a unknown template_name\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network \"$NAME_SPACE\" goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"ACME Profile - 02 - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME Profile - 02 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep unknown\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n\n  - name: \"ACME Profile - 03 - Enroll lego with template submitted in command line (WebServerModified)\"\n    run: |\n      sudo rm -rf lego/\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network \"$NAME_SPACE\" goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d \"lego.$NAME_SPACE\" --http run --profile WebServerModified\n      sudo openssl verify -CAfile cert-1.pem \"lego/certificates/lego.$NAME_SPACE.crt\"\n      sudo openssl x509 -in \"lego/certificates/lego.$NAME_SPACE.crt\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"ACME Profile - 03 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServerModified\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServerModified\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list/action.yml",
    "content": "name: \"enroll_allowed_domain_list\"\ndescription: \"enroll_allowed_domain_list\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll acme.sh with fqdn not part of allowed_domainlist (should fail)\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE  --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.local --alpn --standalone --debug 3 --output-insecure\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Check result \"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll acme.sh with fqdn part of allowed_domainlist\"\n    run: |\n      sudo rm -rf acme-sh/\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE  --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      openssl verify -CAfile cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo/action.yml",
    "content": "name: \"enroll_default_headerinfo\"\ndescription: \"enroll_default_headerinfo\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network \"$NAME_SPACE\" curlimages/curl -f http://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Test if https://acme-srv/directory is accessible \"\n    run: docker run -i --rm --network \"$NAME_SPACE\" curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll acme.sh with template in acme_srv.cfg (WebServer)\"\n    run: |\n      sudo rm -rf acme-sh/\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network \"$NAME_SPACE\"  --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d \"acme-sh.$NAME_SPACE\" --alpn --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < \"acme-sh/acme-sh.${NAME_SPACE}_ecc/ca.cer\"\n      openssl verify -CAfile cert-1.pem \"acme-sh/acme-sh.${NAME_SPACE}_ecc/acme-sh.$NAME_SPACE.cer\"\n      openssl x509 -in \"acme-sh/acme-sh.${NAME_SPACE}_ecc/acme-sh.$NAME_SPACE.cer\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll lego with template in acme_srv.cfg (WebServer)\"\n    run: |\n      sudo rm -rf lego/\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network \"$NAME_SPACE\" goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d \"lego.$NAME_SPACE\" --http run\n      sudo openssl verify -CAfile cert-1.pem \"lego/certificates/lego.$NAME_SPACE.crt\"\n      sudo openssl x509 -in \"lego/certificates/lego.$NAME_SPACE.crt\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll acme.sh with template submitted in command line (WebServerModified)\"\n    run: |\n      sudo rm -rf acme-sh/\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network \"$NAME_SPACE\" --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d \"acme-sh.$NAME_SPACE\" --alpn --standalone --useragent template=WebServerModified --keylength 2048 --debug 3 --output-insecure\n      openssl verify -CAfile cert-1.pem \"acme-sh/acme-sh.$NAME_SPACE/acme-sh.$NAME_SPACE.cer\"\n      openssl x509 -in \"acme-sh/acme-sh.$NAME_SPACE/acme-sh.$NAME_SPACE.cer\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"Enroll lego with template submitted in command line (WebServerModified)\"\n    run: |\n      sudo rm -rf lego/\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network \"$NAME_SPACE\" goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent template=WebServerModified --key-type=rsa2048 -d \"lego.$NAME_SPACE\" --http run\n      sudo openssl verify -CAfile cert-1.pem \"lego/certificates/lego.$NAME_SPACE.crt\"\n      sudo openssl x509 -in \"lego/certificates/lego.$NAME_SPACE.crt\" -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/enroll_eab/action.yml",
    "content": "name: \"enroll_default_headerinfo\"\ndescription: \"enroll_default_headerinfo\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with headerinfo - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 01a - enrollment without header-info field (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 01b - enrollment with header-info field (pick value from list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent template=WebServer --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 01c - enrollment with header-info field containing value not included in list (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent template=Unknown --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: EAB with headerinfo 01c - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 01d - enrollment with header-info field cotaining an invalid parameter (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent ca_name=foo --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 01e - enrollment with header-info field containing parameter not in json (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --user-agent user=user --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 02 -  template from profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 03 - domainlist validation fails (to fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 03 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with headerinfo - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_default_headerinfo\"\ndescription: \"enroll_default_headerinfo\"\ninputs:\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n  TAIL_NUMBER:\n    description: \"Number of lines to tail\"\n    required: false\n    default: \"500\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB with ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 01a - enrollment without profile (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 01a - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServerModified\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServerModified\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n\n  - name: \"EAB with ACME Profile - 01b - enrollment with profile (pick value from list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile WebServer\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage  -noout | grep -i \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 01b - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServer\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServer\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n\n  - name: \"EAB with ACME Profile - 01c - enrollment with profile containing value not included in list (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile Unknown\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile 01c - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 02 -  profile from eab profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 02 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServer\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServer\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n\n  - name: \"EAB with ACME Profile - 03 - domainlist validation fails (to fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 03 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --key-type=rsa2048 -d lego.$NAME_SPACE --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.$NAME_SPACE.crt -ext extendedKeyUsage -noout | grep \"TLS Web Server\"\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n  - name: \"EAB with ACME Profile - 04 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"template: WebServer\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n \"$TAIL_NUMBER\" /var/log/messages | grep \"template: WebServer\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n      TAIL_NUMBER: ${{ inputs.TAIL_NUMBER }}\n"
  },
  {
    "path": ".github/actions/wf_specific/ms_ca_handler/tunnel_setup/action.yml",
    "content": "name: \"tunnel_setup\"\ndescription: \"tunnel_setup\"\ninputs:\n  SSH_KEY:\n    description: \"SSH access key\"\n    required: true\n  SSH_KNOWN_HOSTS:\n    description: \"SSH known hosts\"\n    required: true\n  MSCA_FQDN_WOTLD:\n    description: \"FQDN without top level domain\"\n    required: true\n  MSCA_FQDN:\n    description: \"FQDN\"\n    required: true\n  MSCA_IP:\n    description: \"WCCE host\"\n    required: true\n  SSH_USER:\n    description: \"SSH user\"\n    required: true\n  SSH_HOST:\n    description: \"SSH host\"\n    required: true\n  SSH_PORT:\n    description: \"SSH port\"\n    required: true\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Prepare ssh environment on ramdisk \"\n    run: |\n      sudo mkdir -p /tmp/rd\n      sudo mount -t tmpfs -o size=5M none /tmp/rd\n      sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n      sudo chmod 600 /tmp/rd/ak.tmp\n      sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n    env:\n      SSH_KEY: ${{ inputs.SSH_KEY }}\n      KNOWN_HOSTS: ${{ inputs.SSH_KNOWN_HOSTS }}\n    shell: bash\n\n  - name: \"Setup ssh forwarder\"\n    run: |\n        docker run -d --rm --network $NAME_SPACE --name=$MSCA_FQDN_WOTLD  -e \"MAPPINGS=445:$MSCA_IP:445; 443:$MSCA_IP:443; 88:$MSCA_IP:88\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 443:443 -p 445:445 -p 88:88 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n    env:\n      SSH_USER: ${{ inputs.SSH_USER }}\n      SSH_HOST: ${{ inputs.SSH_HOST }}\n      SSH_PORT: ${{ inputs.SSH_PORT }}\n      MSCA_IP: ${{ inputs.MSCA_IP }}\n      MSCA_FQDN_WOTLD: ${{ inputs.MSCA_FQDN_WOTLD }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n    shell: bash\n\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test conection to mscertsrv via ssh tunnel\"\n    run: |\n      docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$MSCA_FQDN\n    env:\n      MSCA_FQDN: ${{ inputs.MSCA_FQDN }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/nclm_ca_handler/tunnel_setup/action.yml",
    "content": "name: \"tunnel_setup\"\ndescription: \"tunnel_setup\"\ninputs:\n  SSH_KEY:\n    description: \"SSH access key\"\n    required: true\n  SSH_KNOWN_HOSTS:\n    description: \"SSH known hosts\"\n    required: true\n  SSH_USER:\n    description: \"SSH user\"\n    required: true\n  SSH_HOST:\n    description: \"SSH host\"\n    required: true\n  SSH_PORT:\n    description: \"SSH port\"\n    required: true\n  NAME_SPACE:\n    description: \"namespace\"\n    required: true\n    default: \"acme\"\n  NCLM_API_HOST:\n    description: \"NCLM API host\"\n    required: true\n  NCLM_API_USER:\n    description: \"NCLM API user\"\n    required: true\n  NCLM_API_PASSWORD:\n    description: \"NCLM API password\"\n    required: true\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Prepare ssh environment on ramdisk \"\n    run: |\n      sudo mkdir -p /tmp/rd\n      sudo mount -t tmpfs -o size=5M none /tmp/rd\n      sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n      sudo chmod 600 /tmp/rd/ak.tmp\n      sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n    env:\n      SSH_KEY: ${{ inputs.SSH_KEY }}\n      KNOWN_HOSTS: ${{ inputs.SSH_KNOWN_HOSTS }}\n    shell: bash\n\n  - name: \"Setup ssh forwarder\"\n    run: |\n        docker run -d --rm --network $NAME_SPACE --name=forwarder  -e \"MAPPINGS=4000:$NCLM_API_HOST:4000\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 4000:4000 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n    env:\n      SSH_USER: ${{ inputs.SSH_USER }}\n      SSH_HOST: ${{ inputs.SSH_HOST }}\n      SSH_PORT: ${{ inputs.SSH_PORT }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      NCLM_API_HOST: '10.0.0.53' # ${{ inputs.NCLM_API_HOST }}\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test conection to mscertsrv via ssh tunnel\"\n    run: |\n      docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure https://forwarder.acme:4000\n    env:\n      NCLM_API_HOST: ${{ inputs.NCLM_API_HOST }}\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      NCLM_API_USER: ${{ inputs.NCLM_API_USER }}\n      NCLM_API_PASSWORD: ${{ inputs.NCLM_API_PASSWORD }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/openssl_ca_handler/enroll_adjust_cert_validity/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Enroll certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Not After : Jun  9 17:17:00 2030 GMT\"\n    shell: bash\n\n  - name: \"Revoke certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/openssl_ca_handler/enroll_cn_enforce/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Enroll certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Basic Constraints: critical\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Digital Signature, Key Encipherment\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Subject: CN = certbot.acme\"\n    shell: bash\n\n  - name: \"Revoke certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/openssl_ca_handler/enroll_w_teamplate/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Enroll acme.sh\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      # verify aborts due to unhandled critical extension\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Basic Constraints: critical\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"TLS Web Server Authentication, OCSP Signing\"\n    shell: bash\n\n  - name: \"Revoke via acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --revoke --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Enroll certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      # verify aborts due to unhandled critical extension\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Basic Constraints: critical\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"TLS Web Server Authentication, OCSP Signing\"\n    shell: bash\n\n  - name: \"Revoke certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n    shell: bash\n\n  - name: \"Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      # verify aborts due to unhandled critical extension\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Basic Constraints: critical\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"TLS Web Server Authentication, OCSP Signing\"\n    shell: bash\n\n  - name: \"Revoke lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/openxpki_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Enroll lego with without template\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"ACME Profile - 01 - Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 02 - Enroll lego with a unknown template_name\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"ACME Profile - 02 - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME Profile - 02 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep unknown\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"ACME Profile - 03 - Enroll lego with am allowed template_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile tls-client\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME Profile - 03 - Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_profile_name: tls-client\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep \"cert_profile_name: tls-client\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/openxpki_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"rpm\"\n  REVOCATION:\n    description: \"Whether to test revocation as well\"\n    required: true\n    default: \"true\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Clear logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Revoke certificate\"\n    if: ${{ inputs.REVOCATION == 'true' }}\n    run: |\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme revoke\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_profile_name: tls-server\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 800 /var/log/messages | grep \"cert_profile_name: tls-server\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"unknown\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 500 /var/log/messages | grep unknown\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile tls-client\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"Revoke certificate\"\n    if: ${{ inputs.REVOCATION == 'true' }}\n    run: |\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme revoke\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_profile_name: tls-client\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 800 /var/log/messages | grep \"cert_profile_name: tls-client\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"Revoke certificate\"\n    if: ${{ inputs.REVOCATION == 'true' }}\n    run: |\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme revoke\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Check logs\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs | grep \"cert_profile_name: tls-client\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n        docker exec -i acme-srv tail -n 800 /var/log/messages | grep \"cert_profile_name: tls-client\"\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme1.dynamop.de --http run\n    shell: bash\n\n  - name: \"EAB - 04 - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n    shell: bash\n\n  - name: \"Revoke certificate\"\n    if: ${{ inputs.REVOCATION == 'true' }}\n    run: |\n     sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme revoke\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/openxpki_ca_handler/openxpki_prep/action.yml",
    "content": "name: \"OpenXPKI Prep\"\ndescription: \"OpenXPKI preparation steps\"\ninputs:\n  RUNNER_IP:\n    description: \"Runner IP\"\n    required: true\n  WORKING_DIR:\n    description: \"Working directory\"\n    required: true\n    default: ${{ github.workspace }}\n\nruns:\n  using: \"composite\"\n  steps:\n\n  - name: \"Prepare Environment\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      sudo mkdir -p data/acme_ca\n      sudo mkdir -p data/volume/acme_ca\n      mkdir -p /tmp/openxpki\n      sudo chmod -R 777 data\n      sudo sh -c \"echo '$OPENXPKI_IP openxpki' >> /etc/hosts\"\n      sudo cat /etc/hosts\n    env:\n      OPENXPKI_IP: ${{ inputs.RUNNER_IP }}\n    shell: bash\n\n  - name: \"Generate Vault Secret\"\n    working-directory: /tmp/openxpki\n    run: |\n      echo ECN_TOK=$(openssl  rand -hex 32) >> $GITHUB_ENV\n    shell: bash\n\n  - name: \"Instanciate OpenXPKI server\"\n    working-directory: /tmp/openxpki\n    run: |\n      git clone https://github.com/openxpki/openxpki-docker.git\n      cd openxpki-docker/\n      git clone https://github.com/openxpki/openxpki-config.git  --single-branch --branch=community\n\n      # modify configuration according to our needs\n      sed -i \"s/value: 0/value: 1/g\" openxpki-config/config.d/realm/democa/est/default.yaml\n      sed -i \"s/approval_points: 1/approval_points: 0/g\" openxpki-config/config.d/realm/democa/est/default.yaml\n      # sed -i \"s/allow_man_approv: 1/allow_man_approv: 0/g\" openxpki-config/config.d/realm/democa/est/default.yaml\n      sed -i \"s/cert_profile: tls_server/cert_profile: tls_client/g\" openxpki-config/config.d/realm/democa/est/default.yaml\n\n      sed -i \"s/approval_points: 1/approval_points: 0/g\"                   openxpki-config/config.d/realm/democa/rpc/generic.yaml\n      sed -i \"s/export_certificate: chain/export_certificate: fullchain/g\" openxpki-config/config.d/realm/democa/rpc/generic.yaml\n      sed -i \"s/dn: CN=\\[\\% CN.0 \\%\\],DC=Test Deployment,DC=OpenXPKI,DC=org/dn: CN=\\[\\% SAN_DNS.0 \\%\\]/g\" openxpki-config/config.d/realm.tpl/profile/tls_server.yaml\n\n      sudo cp openxpki-config/config.d/realm/democa/rpc/generic.yaml openxpki-config/config.d/realm/democa/rpc/enroll.yaml\n      sudo cp openxpki-config/client.d/service/rpc/generic.yaml openxpki-config/client.d/service/rpc/enroll.yaml\n\n      # add vault secret\n      sed -i \"s/value: you must put your own 64 characters key here/value: $ECN_TOK/g\" openxpki-config/config.d/system/crypto.yaml\n\n      # setup CLI authentication\n      openssl ecparam -name prime256v1 -genkey -noout -out config/client.key\n      chmod 644 config/client.key\n      openssl pkey -in config/client.key -pubout -out config/client.pub\n\n      {\n        echo \"auth:\";\n        echo \"    admin:\";\n        echo \"        key: \";\n        sed 's/^/            /' config/client.pub;\n        echo \"        role: RA Operator\"\n      } > openxpki-config/config.d/system/cli.yaml\n\n      # disable healthcheck for now, as it is not working in our setup and we have to wait for the server to be up before we can do any configuration changes\n      sed -i \"s/test\\: wget -q http:\\/\\/localhost\\/healthcheck\\/ping/test: echo \\\"foo\\\"/g\" docker-compose.yml\n\n      docker compose up -d web --wait\n    shell: bash\n    env:\n      $ECN_TOK: ${{ env.ECN_TOK }}\n\n  - name: \"Sleep for 20s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 20s\n\n  - name: \"Configure OpenXPKI server\"\n    working-directory: /tmp/openxpki/openxpki-docker\n    run: |\n      docker ps\n      docker compose exec -u pkiadm  server /bin/bash /etc/openxpki/contrib/sampleconfig.sh\n    shell: bash\n\n  - name: \"Sleep for 20s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 20s\n\n  - name: \"Enroll keys for Client-authentication via scep\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      sudo openssl genrsa -out data/acme_ca/client_key.pem 2048\n      sudo openssl req -new -key data/acme_ca/client_key.pem -subj '/CN=a2c:pkiclient,O=acme' -outform der | base64 > /tmp/request.pem\n      curl -v -H \"Content-Type: application/pkcs10\" --data @/tmp/request.pem  https://$OPENXPKI_IP:8443/.well-known/est/simpleenroll --insecure  | base64 -d > /tmp/cert.p7b\n\n      # docker exec -u root OpenXPKI_Server cat /var/log/openxpki-server/catchall.log\n      # docker exec -u root OpenXPKI_Server openxpkiadm alias --realm democa\n\n      sudo openssl pkcs7 -print_certs -in /tmp/cert.p7b -inform der -out data/acme_ca/client_crt.pem\n      sudo openssl pkcs12 -export -out data/acme_ca/client_crt.p12 -inkey  data/acme_ca/client_key.pem -in data/acme_ca/client_crt.pem -passout pass:Test1234\n      sudo openssl rsa -noout -modulus -in data/acme_ca/client_key.pem | openssl md5\n      sudo openssl x509 -noout -modulus -in data/acme_ca/client_crt.pem  | openssl md5\n      sudo chmod a+r data/acme_ca/client_key.pem\n      sudo chmod a+r data/acme_ca/client_crt.pem\n      sudo chmod a+r data/acme_ca/client_crt.p12\n      curl https://$OPENXPKI_IP:8443/.well-known/est/cacerts --insecure | base64 -d > /tmp/cacert.p7b\n      sudo openssl pkcs7 -print_certs -in /tmp/cacert.p7b -inform der -out data/acme_ca/ca_bundle.pem\n      sudo chmod a+rw data/acme_ca/ca_bundle.pem\n      sudo openssl s_client -connect $OPENXPKI_IP:8443 2>/dev/null </dev/null |  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >> data/acme_ca/ca_bundle.pem\n      sudo cp data/acme_ca/* data/volume/acme_ca/\n    env:\n      OPENXPKI_IP: ${{ inputs.RUNNER_IP }}\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/upgrade/cleanup/action.yml",
    "content": "name: \"Cleanup after testing upgrade\"\ndescription: \"Cleanup\"\ninputs:\n  DOCKER_COMPOSE_FILE_PATH:\n    description: \"Path to the docker compose file\"\n    required: false\n    default: \"examples/Docker/\"\n  DJANGO_DB:\n    description: \"Database handler\"\n    required: true\n    default: \"sqlite\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Cleanup\"\n      working-directory: ${{ inputs.DOCKER_COMPOSE_FILE_PATH }}\n      run: |\n        if [ -d $(pwd)/data/migrations ]\n        then\n            echo \"delete migration directory\"\n            sudo rm -rf $(pwd)/data/migrations\n        fi\n        if [ -e $(pwd)/data/db.sqlite3 ]\n        then\n            echo \"delete db.sqlite3\"\n            sudo rm -rf $(pwd)/data/db.sqlite3\n        fi\n        if [ -e $(pwd)/data/acme_srv.db ]\n        then\n            echo \"delete acme_srv.db\"\n            sudo rm -rf $(pwd)/data/acme_srv.db\n        fi\n      shell: bash\n\n    - name: \"Instanciate Mariadb\"\n      if: inputs.DJANGO_DB == 'mariadb'\n      uses: ./.github/actions/mariadb_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        INSTANCIATE: \"false\"\n\n    - name: \"Instanciate Postgres\"\n      if: inputs.DJANGO_DB == 'psql'\n      uses: ./.github/actions/psql_prep\n      with:\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n        INSTANCIATE: \"false\"\n"
  },
  {
    "path": ".github/actions/wf_specific/upgrade/enroll/action.yml",
    "content": "name: \"acme-sh, lego, certbot - enroll, renew \"\ndescription: \"Test if acme-sh certbot and lego can enroll and renew cross upgrades\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  RENEWAL:\n    description: \"Renewal method\"\n    required: true\n    default: \"true\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"Create directories\"\n      run: |\n        mkdir -p acme-sh/\n        sudo mkdir -p certbot/\n        sudo mkdir -p lego/ca\n        sudo cp .github/acme2certifier_cabundle.pem certbot/\n        sudo cp .github/acme2certifier_cabundle.pem lego/\n      shell: bash\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test if http://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl -f http://$ACME_SERVER:$HTTP_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Test if https://acme-srv/directory is accessible\"\n      run: docker run -i --rm --network $NAME_SPACE curlimages/curl --insecure -f https://$ACME_SERVER:$HTTPS_PORT/directory\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll lego\"\n      run: |\n        echo \"##### HTTP - Enroll lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls run\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll acme.sh\"\n      run: |\n        echo \"##### HTTPS - Enroll acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --debug 1 --output-insecure --insecure\n          ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --issue --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --keylength 2048 --debug 1 --output-insecure --insecure\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Enroll certbot\"\n      run: |\n        echo \"##### HTTPS - Enroll certbot #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Renewal\"\n      if: ${{ inputs.RENEWAL == 'true' }}\n      uses: ./.github/actions/wf_specific/upgrade/renew\n      with:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n"
  },
  {
    "path": ".github/actions/wf_specific/upgrade/renew/action.yml",
    "content": "name: \"acme_clients - renew certificates\"\ndescription: \"Test if acme.sh, certbot and lego can renew certificates across upgrades\"\ninputs:\n  ACME_SERVER:\n    description: \"ACME server URL\"\n    required: true\n    default: \"acme-srv\"\n  REVOCATION:\n    description: \"Revocation method\"\n    required: true\n    default: \"true\"\n  CLEANUP:\n    description: \"Cleanup method\"\n    required: true\n    default: \"false\"\n  USE_RSA:\n    description: \"Use RSA\"\n    required: true\n    default: \"false\"\n  HTTP_PORT:\n    description: \"HTTP port\"\n    required: true\n    default: \"80\"\n  HTTPS_PORT:\n    description: \"HTTPS port\"\n    required: true\n    default: \"443\"\n  HOSTNAME_SUFFIX:\n    description: \"Hostname suffix\"\n    required: true\n  NAME_SPACE:\n    description: \"Namespace\"\n    required: true\n    default: \"acme\"\n\nruns:\n  using: \"composite\"\n  steps:\n\n    - name: \"HTTPS - Renew lego\"\n      run: |\n        echo \"##### HTTP - Renew lego #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls renew --days 364 --no-random-sleep\n        else\n          echo \"use RSA\"\n          docker run -i --rm -e LEGO_CA_CERTIFICATES=.lego/acme2certifier_cabundle.pem -v $PWD/lego:/.lego/ --name lego$HOSTNAME_SUFFIX --network $NAME_SPACE goacme/lego --tls-skip-verify -s https://$ACME_SERVER:$HTTPS_PORT -a --email \"lego@example.com\" --key-type=rsa2048 -d lego$HOSTNAME_SUFFIX.$NAME_SPACE --tls renew --days 364 --no-random-sleep\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Renew acme.sh\"\n      run: |\n        echo \"##### HTTPS - Renew acme.sh #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          echo \"use ECC\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --renew --force --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --debug 1 --output-insecure --insecure\n          ECC=\"_ecc\"\n        else\n          echo \"use RSA\"\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network $NAME_SPACE --name acme-sh$HOSTNAME_SUFFIX neilpang/acme.sh:latest --renew --force --server https://$ACME_SERVER:$HTTPS_PORT --accountemail 'acme-sh@example.com' -d acme-sh$HOSTNAME_SUFFIX.$NAME_SPACE --alpn --standalone --keylength 2048 --debug 1 --output-insecure --insecure\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTP_PORT: ${{ inputs.HTTP_PORT }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"HTTPS - Renew certbot\"\n      if: ${{ inputs.USE_CERTBOT == 'true' }}\n      run: |\n        echo \"##### HTTPS - Renew certbot #####\"\n        if [ \"$USE_RSA\" == \"false\" ]; then\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120  --force-renewal\n        else\n          docker run -i --rm --name certbot$HOSTNAME_SUFFIX --network $NAME_SPACE -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://$ACME_SERVER:$HTTPS_PORT --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' --key-type rsa -d certbot$HOSTNAME_SUFFIX.$NAME_SPACE --cert-name certbot --issuance-timeout 120 --force-renewal\n        fi\n      shell: bash\n      env:\n        ACME_SERVER: ${{ inputs.ACME_SERVER }}\n        HTTPS_PORT: ${{ inputs.HTTPS_PORT }}\n        USE_RSA: ${{ inputs.USE_RSA }}\n        HOSTNAME_SUFFIX: ${{ inputs.HOSTNAME_SUFFIX }}\n        NAME_SPACE: ${{ inputs.NAME_SPACE }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Cleanup certificates\"\n      if: ${{ inputs.CLEANUP == 'true' }}\n      run: |\n        echo \"##### Cleanup certificates #####\"\n        sudo rm -rf lego/*\n        sudo rm -rf acme-sh/*\n        sudo rm -rf certbot/*\n      shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/vault_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME Profile - 01a - enrollment without profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i \"ACME Intermediate Authority\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"IPSec Tunnel\"\n    shell: bash\n\n  - name: \"ACME Profile - 02 - Allowed profile\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile clientauth\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i \"ACME Intermediate Authority\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"IPSec User\"\n    shell: bash\n\n  - name: \"ACME Profile - 03 - enrollment with unknown profile (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: ACME Profile 03 - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/vault_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_w_headerinfo\"\ndescription: \"enroll_w_headerinfo\"\ninputs:\n  ASA_CA_NAME1:\n    description: \"ASA CA 1\"\n    required: true\n  ASA_CA_NAME2:\n    description: \"ASA CA 2\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB ACME Profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB wit headerinfo - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01a - enrollment without profile (first value in list)\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i \"ACME Intermediate Authority\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"IPSec User\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01b - enrollment with profile (pick value from list)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile serverauth\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i \"ACME Intermediate Authority\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"IPSec Tunnel\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01c - enrollment with profile containing value not included in list (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --http run --profile unknown\n    shell: bash\n\n  - name: EAB ACME Profile 01c - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profile - 01d - enrollment with profile containing parameter not in json (silent overwrite)\"\n    run: |\n      sudo rm -rf lego/*\n      sudo docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\"  -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --key-type rsa2048 --http run --profile serverauth\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout  | grep -i root.acme\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 02 -  profiling ca and vault role\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --key-type rsa2048 --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout  | grep -i root.acme\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"TLS Web Client\"\n    shell: bash\n\n  - name: \"EAB ACME Profile - 02 -  revoke profiled ca and vault role\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --http revoke\n    shell: bash\n\n  - name: \"EAB ACME Profile - 03 - domainlist validation fails (to fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --http run\n    shell: bash\n\n  - name: \"EAB ACME Profile - 03 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB ACME Profile - 04 - Settings from acme_srv.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer -noout | grep -i \"ACME Intermediate Authority\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout | grep \"IPSec Tunnel\"\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/vault_ca_handler/vault_prep/action.yml",
    "content": "name: \"vault_prep\"\ndescription: \"vault_prep\"\ninputs:\n  RUNNER_IP:\n    description: \"Runner IP\"\n    required: true\n  WORKING_DIR:\n    description: \"Working directory\"\n    required: true\n    default: ${{ github.workspace }}\n  ISSUING_CA_KEY:\n    description: \"Path to the Issuing-CA private key\"\n    required: true\n    default: \"test/ca/sub-ca-key.pem\"\n  ISSUING_CA_CERT:\n    description: \"Path to the CA certificate\"\n    required: true\n    default: \"test/ca/sub-ca-cert.pem\"\n  ISSUING_CA_PASSPHRASE:\n    description: \"Passphrase for the private key\"\n    required: true\n    default: \"Test1234\"\n  ROOT_CA_CERT:\n    description: \"Path to the root CA certificate\"\n    required: true\n    default: \"test/ca/root-ca-cert.pem\"\n  NAME_SPACE:\n    description: \"Name space for the Docker network\"\n    required: true\n    default: \"acme\"\n\noutputs:\n  VAULT_TOKEN:\n    description: \"Vault Token\"\n    value: ${{ env.VAULT_TOKEN }}\n  ISSUER_REF:\n    description: \"Issuer Reference\"\n    value: ${{ env.ISSUER_REF }}\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Prepare Environment\"\n    working-directory: ${{ inputs.WORKING_DIR }}\n    run: |\n      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n      echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n      sudo apt update\n      sudo apt install -y docker-compose-plugin\n      # docker network create acme\n      mkdir -p vault/config\n      sudo chmod -R 777 vault\n      sudo sh -c \"echo '$VAULT_IP vault' >> /etc/hosts\"\n      cp vault/config.hcl vault/config/\n      cp vault/compose.yaml vault/docker-compose.yaml\n    env:\n      VAULT_IP: ${{ inputs.RUNNER_IP }}\n    shell: bash\n\n  - name: \"Generate Certificates\"\n    run: |\n      mkdir -p $WORKING_DIR/vault/certs\n      openssl req -nodes -newkey rsa:2048 -keyout $WORKING_DIR/vault/certs/server.key -out $WORKING_DIR/vault/certs/server.csr -batch -subj \"/CN=vault.acme\" -addext \"subjectAltName=DNS:vault.acme\" -addext \"keyUsage = digitalSignature, keyEncipherment, dataEncipherment\" -addext \"extendedKeyUsage = serverAuth\" -addext \"basicConstraints=CA:false\"\n      openssl x509 -req -in $WORKING_DIR/vault/certs/server.csr -CA $ISSUING_CA_CERT -CAkey $ISSUING_CA_KEY -CAcreateserial -out $WORKING_DIR/vault/certs/server.crt -copy_extensions copy -days 30 -sha256 --passin pass:$ISSUING_CA_PASSPHRASE\n      cat $ISSUING_CA_CERT >> $WORKING_DIR/vault/certs/server.csr\n      cat $ROOT_CA_CERT >> $WORKING_DIR/vault/certs/server.csr\n      sudo chmod 777 $WORKING_DIR/vault/*\n    shell: bash\n    env:\n      WORKING_DIR: ${{ inputs.WORKING_DIR }}\n      ISSUING_CA_CERT: ${{ inputs.ISSUING_CA_CERT }}\n      ISSUING_CA_KEY: ${{ inputs.ISSUING_CA_KEY }}\n      ISSUING_CA_PASSPHRASE: ${{ inputs.ISSUING_CA_PASSPHRASE }}\n      ROOT_CA_CERT: ${{ inputs.ROOT_CA_CERT }}\n\n  - name: \"Instanciate vault server\"\n    working-directory: ${{ inputs.WORKING_DIR }}/vault\n    run: |\n      docker compose up -d\n    shell: bash\n\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Configure vault server\"\n    working-directory: ${{ inputs.WORKING_DIR }}/vault\n    run: |\n      docker ps -a\n      VAULT_INIT_OUTPUT=\"$(docker exec vault vault operator init)\"\n      sleep 2\n      UNSEAL_KEY_1=$(echo \"$VAULT_INIT_OUTPUT\" | sed -n 's/.*Unseal Key 1: \\([^ ]*\\).*/\\1/p')\n      UNSEAL_KEY_2=$(echo \"$VAULT_INIT_OUTPUT\" | sed -n 's/.*Unseal Key 2: \\([^ ]*\\).*/\\1/p')\n      UNSEAL_KEY_3=$(echo \"$VAULT_INIT_OUTPUT\" | sed -n 's/.*Unseal Key 3: \\([^ ]*\\).*/\\1/p')\n      ROOT_TOKEN=$(echo \"$VAULT_INIT_OUTPUT\" | sed -n 's/.*Initial Root Token: \\([^ ]*\\).*/\\1/p')\n      echo VAULT_TOKEN=$ROOT_TOKEN >> $GITHUB_ENV\n      echo \"Unseal Key 1: $UNSEAL_KEY_1\"\n      echo \"Unseal Key 2: $UNSEAL_KEY_2\"\n      echo \"Unseal Key 3: $UNSEAL_KEY_3\"\n      echo \"Root Token: $ROOT_TOKEN\"\n      echo \"Vault Token: ${{ env.VAULT_TOKEN }}\"\n\n      docker exec vault vault operator unseal $UNSEAL_KEY_1\n      docker exec vault vault operator unseal $UNSEAL_KEY_2\n      docker exec vault vault operator unseal $UNSEAL_KEY_3\n      echo $ROOT_TOKEN | docker exec -i vault vault login -\n\n      # Create root ca\n      docker exec vault vault secrets enable pki\n      docker exec vault vault secrets tune -max-lease-ttl=87600h pki\n\n      docker exec vault vault write -field=certificate pki/root/generate/internal \\\n          common_name=\"root.acme\" \\\n          issuer_name=\"root\" \\\n          ttl=87600h > root_ca.crt\n\n      docker exec vault vault list pki/issuers/\n      docker exec vault vault write pki/roles/servers allow_any_name=true\n\n      docker exec vault vault write pki/config/urls \\\n          issuing_certificates=\"http://127.0.0.1:8200/v1/pki/ca\" \\\n          crl_distribution_points=\"http://127.0.0.1:8200/v1/pki/crl\"\n\n      ROOT_ISSUER_REF=$(docker exec vault sh -c 'vault list -format=json pki/issuers/' | jq -r '.[]')\n      echo $ROOT_ISSUER_REF\n\n      # Intermediate CA\n      docker exec vault vault secrets enable -path=pki_int pki\n      docker exec vault vault secrets tune -max-lease-ttl=43800h pki_int\n\n      docker exec vault vault pki issue \\\n            --issuer_name=acme-intermediate \\\n            /pki/issuer/$ROOT_ISSUER_REF \\\n            /pki_int/ \\\n            common_name=\"ACME Intermediate Authority\" \\\n            key_type=\"rsa\" \\\n            key_bits=\"4096\" \\\n            max_depth_len=1 \\\n            ttl=\"43800h\"\n\n      # docker exec vault vault read -field=default pki_int/config/issuers\n\n      ISSUER_REF=$(docker exec vault vault read -field=default pki_int/config/issuers)\n      echo $ISSUER_REF\n\n\n      # Create roles\n      docker exec vault vault write pki_int/roles/bar-dot-local \\\n          issuer_ref=\"$ISSUER_REF\" \\\n          allowed_domains=\"bar.local\" \\\n          allow_subdomains=true \\\n          key_type=rsa\\\n          key_usage=\"DigitalSignature, KeyEncipherment\" \\\n          ext_key_usage=\"ServerAuth\"  \\\n          max_ttl=\"720h\"\n\n      docker exec vault vault write pki_int/roles/serverauth \\\n          issuer_ref=\"$ISSUER_REF\" \\\n          allowed_domains=\"acme\" \\\n          allow_subdomains=true \\\n          key_type=ec \\\n          key_usage=\"DigitalSignature, KeyEncipherment\" \\\n          ext_key_usage=\"ServerAuth, IPSecTunnel\"  \\\n          max_ttl=\"720h\"\n\n      docker exec vault vault write pki_int/roles/clientauth \\\n          issuer_ref=\"$ISSUER_REF\" \\\n          allowed_domains=\"acme\" \\\n          allow_subdomains=true \\\n          key_type=ec \\\n          key_usage=\"DigitalSignature, KeyEncipherment\" \\\n          ext_key_usage=\"ClientAuth, IPSECUser\"  \\\n          max_ttl=\"720h\"\n    shell: bash\n\n  - name: \"Test if vault is accessible\"\n    run: |\n      docker run --rm --network $NAME_SPACE curlimages/curl \\\n        --header \"X-Vault-Token: $VAULT_TOKEN\" --header \"X-Vault-Namespace: admin\" \\\n        https://vault:8200/v1/pki_int/issue/serverauth --insecure -v --request POST \\\n        --data '{\"common_name\": \"test1.acme\", \"ttl\": \"24h\"}' | jq\n    shell: bash\n    env:\n      NAME_SPACE: ${{ inputs.NAME_SPACE }}\n      VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_acmeprofile/action.yml",
    "content": "name: \"enroll_headerinfo\"\ndescription: \"enroll_headerinfo\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"ACME-profile - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME-profile - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"ACME-profile - 01 - Enroll lego without template_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"ACME-profile - 02 - Enroll lego with template_name template\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile template\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"ACME-profile - 03 - Enroll lego with template_name acme (to fail)\"\n    id: legoprofilefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run --profile unknown\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 03 - check  result \"\n    if: steps.legoprofilefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.legoprofilefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n      sudo rm -rf  acme-sh/*\n      sudo rm -rf  certbot/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_eab/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll acme with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 02a - Enroll acme with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent template_name=unknown -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 02b - Enroll acme with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent template_name=acme -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 02a - Enroll lego with a template_name taken from header_info NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent template_name=unknown -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 02b - Enroll lego with a template_name taken from header_info included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --user-agent template_name=acme -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 03 - Enroll acme with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Issuer: CN = root-ca\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Issuer: CN = root-ca\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 04 - Enroll acme with a not allowed fqdn in kid.json (to fail)\"\n    id: acmefail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 04 - check  result \"\n    if: steps.acmefail02.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll acme with default values from acme.cfg\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_03 --eab-hmac-key YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 06 - Enroll acme with not allowed headerinfo-field (should fail)\"\n    id: acmefail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --useragent template_name=acme -d acme-sh.acme --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 06 - check  result \"\n    if: steps.acmefail03.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 06 - Enroll lego with not allowed headerinfo-field (should fail)\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --user-agent template_name=acme -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB - 06 - check  result \"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 07a - Enroll acme with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -text -noout | grep \"Issuer: CN = root-ca\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 07a - Enroll lego with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d www.example.local --http run\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -noout | grep \"Issuer: CN = root-ca\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 07b - Enroll acme - challenge validations fails\"\n    id: acmefail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 07b - check  result \"\n    if: steps.acmefail07.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail07.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 07b - Enroll lego - challenge validations fails\"\n    id: legofail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d www.example.local --http run\n    shell: bash\n\n  - name: \"EAB - 07b - check  result \"\n    if: steps.legofail07.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail07.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_eab_acmeprofile/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"EAB - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - 01 - Enroll lego with a template_name taken from list in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 02a - Enroll lego with a template_name taken from profile NOT included in kid.json (to fail)\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile unknown\n    shell: bash\n\n  - name: \"EAB - 02a - check  result \"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 02b - Enroll lego with a template_name taken from profile included in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw -d lego.acme --http run --profile acme\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 03 - Enroll lego with a template_name/ca_name taken from kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout | grep \"CN = root-ca\"\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 03 - Revoke lego with a profiled ca_name taken from kid.json\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d lego.acme revoke\n    shell: bash\n\n  - name: \"EAB - 04 - Enroll lego with a not allowed fqdn in kid.json (to fail)\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n    shell: bash\n\n  - name: \"EAB - 04a - check  result \"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05 - Enroll lego with default values from acme.cfg\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_03 --hmac YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr -d lego.acme --http run\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -issuer --noout\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"EAB - 07a - Enroll acme with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -text -noout | grep \"Issuer: CN = root-ca\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 07a - Enroll lego with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d www.example.local --http run\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -noout | grep \"Issuer: CN = root-ca\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 07b - Enroll acme - challenge validations fails\"\n    id: acmefail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 07b - check  result \"\n    if: steps.acmefail07.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail07.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 07b - Enroll lego - challenge validations fails\"\n    id: legofail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d www.example.local --http run\n    shell: bash\n\n  - name: \"EAB - 07b - check  result \"\n    if: steps.legofail07.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail07.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_eab_sp/action.yml",
    "content": "name: \"enroll_eab\"\ndescription: \"enroll_eab\"\ninputs:\n  DEPLOYMENT_TYPE:\n    description: \"Deployment type\"\n    required: true\n    default: \"container\"\n\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"EAB - Sleep for 10s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 10s\n\n  - name: \"EAB - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"EAB SP - 01a - SUCC - Enroll acme - 1st list entry\"\n    run: |\n      mkdir -p acme-sh\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=acme corp/OU=acme1/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"EAB SP - 01a - SUCC - Enroll lego - 1st list entry\"\n    run: |\n      sudo mkdir -p lego\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=acme corp/OU=acme1/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"EAB SP - 01B - SUCC - Enroll acme - 2nd list entry\"\n    run: |\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=acme corp/OU=acme2/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"EAB SP - 01B - SUCC - Enroll lego - 2nd list entry\"\n    run: |\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=acme corp/OU=acme2/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"EAB SP - 01C - FAIL - Enroll acme - entry not in list\"\n    id: acmefail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=acme corp/OU=acme3/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.acmefail01.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"organizationalUnitName: value: acme3 expected: \\['acme1', 'acme2'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"organizationalUnitName: value: acme3 expected: \\['acme1', 'acme2'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 01C - Fail - Enroll lego - entry not in list\"\n    id: legofail01\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=acme corp/OU=acme3/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.legofail01.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"organizationalUnitName: value: acme3 expected: \\['acme1', 'acme2'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"organizationalUnitName: value: acme3 expected: \\['acme1', 'acme2'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 02 - FAIL - Enroll acme - wildcard entry not present\"\n    id: acmefail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=acme corp/OU=acme1/C=AC' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.acmefail02.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: \\['serialNumber'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: \\['serialNumber'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 02 - FAIL - Enroll lego - wildcard entry not present\"\n    id: legofail02\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=acme corp/OU=acme1/C=AC' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.legofail02.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: \\['serialNumber'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: \\['serialNumber'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 03 - FAIL - Enroll acme - string check failed\"\n    id: acmefail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=noacme corp/OU=acme2/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.acmefail03.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: organizationName: value: noacme corp expected: acme corp\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: organizationName: value: noacme corp expected: acme corp\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 03 - FAIL - Enroll lego - string check failed\"\n    id: legofail03\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=noacme corp/OU=acme2/C=AC/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.legofail03.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail03.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: organizationName: value: noacme corp expected: acme corp\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: organizationName: value: noacme corp expected: acme corp\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 04 - FAIL - Enroll acme - string parameter not present\"\n    id: acmefail04\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      openssl genrsa -out acme-sh/acme-sh.acme.key 2048\n      openssl req -new -key acme-sh/acme-sh.acme.key -subj '/CN=acme-sh.acme/O=acme corp/OU=acme2/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:acme-sh.acme\" -outform pem -out acme-sh/acme-sh.acme.csr\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_00 --eab-hmac-key V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name acme-sh neilpang/acme.sh:latest --signcsr --csr acme.sh/acme-sh.acme.csr --server http://acme-srv --standalone --debug 1 --output-insecure --insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.acmefail04.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail04.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: \\['countryName'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: \\['countryName'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB SP - 04 - FAIL - Enroll acme - string parameter not present\"\n    id: legofail04\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      sudo openssl genrsa -out lego/lego.acme.key 2048\n      sudo openssl req -new -key lego/lego.acme.key -subj '/CN=lego.acme/O=acme corp/OU=acme2/serialNumber=00-11-22-33' --addext \"subjectAltName = DNS:lego.acme\" -outform pem -out lego/lego.acme.csr\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_00 --hmac V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw --csr .lego/lego.acme.csr --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n    shell: bash\n\n  - name: \"Check result\"\n    if: steps.legofail04.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail04.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"Sleep for 2s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 2s\n\n  - name: \"Check logs for errors\"\n    working-directory: examples/Docker/\n    run: |\n      if [ \"$DEPLOYMENT_TYPE\" == \"container\" ]; then\n        docker compose logs > docker-compose.log\n        cat docker-compose.log | grep \"failed for: \\['countryName'\\]\"\n        sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n      # elif [ \"$DEPLOYMENT_TYPE\" == \"rpm\" ]; then\n      #  docker exec acme-srv grep \"failed for: \\['countryName'\\]\" /var/log/messages\n      fi\n    shell: bash\n    env:\n      DEPLOYMENT_TYPE: ${{ inputs.DEPLOYMENT_TYPE }}\n\n  - name: \"EAB - 05a - Enroll acme with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_01 --eab-hmac-key YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -text -noout | grep \"Issuer: CN = root-ca\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/www.example.local_ecc/www.example.local.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 05a - Enroll lego with challenge validation disabled in kid.json\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_01 --hmac YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg -d www.example.local --http run\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -ext extendedKeyUsage -noout\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -noout | grep \"Issuer: CN = root-ca\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/www.example.local.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"EAB - 05b - Enroll acme - challenge validations fails\"\n    id: acmefail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d www.example.local --standalone --debug 3 --output-insecure\n    shell: bash\n\n  - name: \"EAB - 05b - check  result \"\n    if: steps.acmefail07.outcome != 'failure'\n    run: |\n      echo \"acmefail outcome is ${{steps.acmefail07.outcome }}\"\n      exit 1\n    shell: bash\n\n  - name: \"EAB - 05b - Enroll lego - challenge validations fails\"\n    id: legofail07\n    continue-on-error: true\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d www.example.local --http run\n    shell: bash\n\n  - name: \"EAB - 05b - check  result \"\n    if: steps.legofail07.outcome != 'failure'\n    run: |\n      echo \"legofail outcome is ${{steps.legofail07.outcome }}\"\n      exit 1\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_headerinfo/action.yml",
    "content": "name: \"enroll_headerinfo\"\ndescription: \"enroll_headerinfo\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Header-info - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Header-info - 01 - Enroll acme.sh without template_name\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Header-info - 01 - Enroll lego without template_name\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Header-info - 02 - Enroll acme.sh with template_name template\"\n    run: |\n      sudo rm -rf acme-sh/*\n      # docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --debug 3\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' --useragent template_name=template -d acme-sh.acme --standalone --debug 3 --output-insecure\n      awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature, Non Repudiation\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -ext extendedKeyUsage -noout | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"Header-info - 02 - Enroll lego with template_name template\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent template_name=template -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext keyUsage | grep \"Digital Signature, Non Repudiation\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -ext extendedKeyUsage | grep \"TLS Web Client Authentication, Code Signing\"\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n      sudo rm -rf  acme-sh/*\n      sudo rm -rf  certbot/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_no_template/action.yml",
    "content": "name: \"enroll_no_template\"\ndescription: \"enroll_no_template\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"No template - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"No template - Enroll acme.sh\"\n    run: |\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Basic Constraints: critical\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Digital Signature, Key Encipherment\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"No template - Register certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"No template - Enroll HTTP-01 single domain certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Basic Constraints: critical\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Digital Signature, Key Encipherment\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"No template - Enroll lego\"\n    run: |\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Basic Constraints: critical\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Digital Signature, Key Encipherment\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"TLS Web Server Authentication\"\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n      sudo rm -rf  acme-sh/*\n      sudo rm -rf  certbot/*\n    shell: bash\n"
  },
  {
    "path": ".github/actions/wf_specific/xca_ca_handler/enroll_template/action.yml",
    "content": "name: \"enroll_template\"\ndescription: \"enroll_template\"\n\nruns:\n  using: \"composite\"\n  steps:\n  - name: \"Sleep for 5s\"\n    uses: juliangruber/sleep-action@v2.0.3\n    with:\n      time: 5s\n\n  - name: \"Template - Test http://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n    shell: bash\n\n  - name: \"Template - Test if https://acme-srv/directory is accessible\"\n    run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n    shell: bash\n\n  - name: \"Template - Enroll acme.sh\"\n    run: |\n      sudo rm -rf acme-sh/*\n      docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure\n      openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -text -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Template - Register certbot\"\n    run: |\n      sudo rm -rf certbot/*\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n    shell: bash\n\n  - name: \"Template - Enroll certbot\"\n    run: |\n      docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  certbot/live/certbot/cert.pem\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in certbot/archive/certbot/cert1.pem -text -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Template - Enroll lego\"\n    run: |\n      sudo rm -rf lego/*\n      docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n      sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  lego/certificates/lego.acme.crt\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"Digital Signature, Non Repudiation, Key Encipherment, Key Agreement\"\n      sudo openssl x509 -in lego/certificates/lego.acme.crt -text -noout | grep \"TLS Web Server Authentication, TLS Web Client Authentication\"\n    shell: bash\n\n  - name: \"Delete acme-sh, letsencypt and lego folders\"\n    run: |\n      sudo rm -rf  lego/*\n      sudo rm -rf  acme-sh/*\n      sudo rm -rf  certbot/*\n    shell: bash\n"
  },
  {
    "path": ".github/django_settings.py",
    "content": "\"\"\"\nDjango settings for acme2certifier project.\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/\n\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"127.0.0.1\", \"*\"]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"acme_srv\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nROOT_URLCONF = \"acme2certifier.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \"acme2certifier.wsgi.application\"\n\n\n# Database\n# https://docs.djangoproject.com/en/1.11/ref/settings/#databases\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": os.path.join(BASE_DIR, \"/var/www/acme2certifier/volume/db.sqlite3\"),\n    }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/1.11/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/1.11/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\n"
  },
  {
    "path": ".github/django_settings_mariadb.py",
    "content": "\"\"\"\nDjango settings for acme2certifier project.\n\nGenerated by 'django-admin startproject' using Django 1.11.15.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/1.11/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/1.11/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/\n\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"127.0.0.1\", \"*\"]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"acme_srv\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nROOT_URLCONF = \"acme2certifier.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \"acme2certifier.wsgi.application\"\n\n\n# Database\n# https://docs.djangoproject.com/en/1.11/ref/settings/#databases\n\nDATABASES = {\n    #'default': {\n    #    'ENGINE': 'django.db.backends.sqlite3',\n    #    'NAME': os.path.join(BASE_DIR, '/var/www/acme2certifier/volume/db.sqlite3'),\n    # }\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"XXX\": \"XXX\",  # --- IGNORE ---\n        \"HOST\": \"mariadbsrv.acme\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/1.11/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/1.11/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\n"
  },
  {
    "path": ".github/django_settings_mssql.py",
    "content": "\"\"\"\nDjango settings for acme2certifier project.\n\nGenerated by 'django-admin startproject' using Django 1.11.15.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/1.11/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/1.11/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/\n\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"127.0.0.1\", \"*\"]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"acme_srv\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nROOT_URLCONF = \"acme2certifier.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \"acme2certifier.wsgi.application\"\n\n\n# Database\n# https://docs.djangoproject.com/en/1.11/ref/settings/#databases\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"mssql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier_user\",\n        \"XXX\": \"XXX\",  # --- IGNORE ---\n        \"HOST\": \"ms-sql.acme\",\n        \"PORT\": \"1433\",\n        \"OPTIONS\": {\n            \"driver\": \"ODBC Driver 18 for SQL Server\",\n            \"extra_params\": \"Encrypt=no;TrustServerCertificate=yes\",\n        },\n    }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/1.11/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/1.11/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\n"
  },
  {
    "path": ".github/django_settings_psql.py",
    "content": "\"\"\"\nDjango settings for acme2certifier project.\n\nGenerated by 'django-admin startproject' using Django 1.11.15.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/1.11/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/1.11/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/\n\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"127.0.0.1\", \"*\"]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"acme_srv\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nROOT_URLCONF = \"acme2certifier.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \"acme2certifier.wsgi.application\"\n\n\n# Database\n# https://docs.djangoproject.com/en/1.11/ref/settings/#databases\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql_psycopg2\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"XXX\": \"XXX\",  # --- IGNORE ---\n        \"HOST\": \"postgresdbsrv\",\n        \"PORT\": \"\",\n    }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/1.11/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/1.11/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\n"
  },
  {
    "path": ".github/dns_test.sh",
    "content": "#!/usr/bin/env sh\n\ndns_test_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"adding dns record: ${fulldomain}: ${txtvalue}\"\n  echo \"txt-record=${fulldomain},\\\"${txtvalue}\\\"\" >> /dnsmasq.conf\n  killall -9 dnsmasq\n  _sleep 1\n  dnsmasq -C /dnsmasq.conf\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_test_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"removing dns record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n#  grep -v \"txt-record=${fulldomain},\\\"${txtvalue}\\\"\" /dnsmasq.conf > /dnsmasq.conf\n#  killall -9 dnsmasq\n#  dnsmasq -C /dnsmasq.conf\nreturn 0\n}\n"
  },
  {
    "path": ".github/dnsmasq.conf",
    "content": "log-queries\nno-resolv\nserver=1.0.0.1\nserver=1.1.1.1\nstrict-order\naddress=/www.bar.local/RUNNER_IP\n"
  },
  {
    "path": ".github/dnsmasq.yml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: dnsmasq\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: dnsmasq\n  namespace: dnsmasq\n  labels:\n    app: dns-masq\nspec:\n  hostname: dnsmasq\n  containers:\n    - name: dnsmasq\n      image: gigantuar/dnsmasq:latest-amd64\n      imagePullPolicy: Never\n      volumeMounts:\n        - mountPath: /etc/dnsmasq.conf\n          name: dnscfg\n          subPath: dnsmasq.conf\n  volumes:\n    - name: dnscfg\n      hostPath:\n        path: RUNNER_PATH/data\n        type: Directory\n"
  },
  {
    "path": ".github/est_handler.patch",
    "content": "6a7,8\n> import urllib3\n> requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL:@SECLEVEL=1'\n"
  },
  {
    "path": ".github/k8s-acme-srv.yml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: cert-manager-acme\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: acme2certifier\n  namespace: cert-manager-acme\n  labels:\n    app: a2c\nspec:\n  hostname: acme-srv\n  dnsPolicy: \"None\"\n  automountServiceAccountToken: false\n  dnsConfig:\n    nameservers:\n      - DNSMASQ_IP\n  containers:\n    - name: acme2certifier\n      resources:\n        requests:\n          cpu: \"250m\"\n          memory: \"256Mi\"\n          ephemeral-storage: \"1Gi\"\n        limits:\n          memory: \"512Mi\"\n          ephemeral-storage: \"1Gi\"\n      image: grindsa/acme2certifier:devel\n      imagePullPolicy: Never\n      ports:\n        - containerPort: 80\n      volumeMounts:\n        - mountPath: /var/www/acme2certifier/volume/\n          name: a2c-volume\n  volumes:\n    - name: a2c-volume\n      hostPath:\n        path: /home/runner/work/acme2certifier/acme2certifier/data\n        type: Directory\n"
  },
  {
    "path": ".github/k8s-cert-mgr-dns-01.yml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: cloudflare-api-token-secret\n  namespace: cert-manager-acme\ntype: Opaque\nstringData:\n  api-token: CF_TOKEN\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: acme2certifier\n  namespace: cert-manager-acme\nspec:\n  acme:\n    email: foo@bar.local\n    server: http://ACME_SRV/directory\n    privateKeySecretRef:\n      name: issuer-account-key\n    solvers:\n      - dns01:\n          cloudflare:\n            email: MY_EMAIL\n            apiTokenSecretRef:\n              name: cloudflare-api-token-secret\n              key: api-token\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: acme-cert\n  namespace: cert-manager-acme\nspec:\n  secretName: k8-acme-secret\n  issuerRef:\n    name: acme2certifier\n  commonName: k8.acme.dynamop.de\n  dnsNames:\n    - k8.acme.dynamop.de\n\n  renewBefore: 48h\n"
  },
  {
    "path": ".github/k8s-cert-mgr-http-01.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: webserver-depl\nspec:\n  selector:\n    matchLabels:\n      app: webserver-app\n  template:\n    metadata:\n      labels:\n        app: webserver-app\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: webserver-app\n          image: nginx:1.8\n          resources:\n            requests:\n              cpu: \"100m\"\n              memory: \"128Mi\"\n              ephemeral-storage: \"1Gi\"\n            limits:\n              cpu: \"500m\"\n              memory: \"256Mi\"\n              ephemeral-storage: \"1Gi\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: webserver-svc\nspec:\n  selector:\n    app: webserver-app\n  ports:\n    - name: webserver-app\n      protocol: TCP\n      port: 80\n      targetPort: 80\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: ingress-routes\n  annotations:\n    cert-manager.io/cluster-issuer: \"acme2certifier\"\n    # acme.cert-manager.io/http01-edit-in-place: \"true\"\nspec:\n  tls:\n    - hosts:\n        - www.bar.local\n      secretName: tls-secret\n  rules:\n    - host: www.bar.local\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: webserver-svc\n                port:\n                  number: 80\n  ingressClassName: public\n\n---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: acme2certifier\nspec:\n  acme:\n    server: http://ACME_SRV/directory\n    email: grindsa@bar.local\n    privateKeySecretRef:\n      name: a2c\n    solvers:\n      - http01:\n          ingress:\n            ingressTemplate:\n              metadata:\n                annotations:\n                  ingressClassName: public\n"
  },
  {
    "path": ".github/mlc_config.json",
    "content": "{\n  \"projectBaseUrl\":\"${workspaceFolder}\",\n  \"ignorePatterns\": [\n    {\n      \"pattern\": \"^ https://hub.docker.com\"\n    }\n  ],\n  \"replacementPatterns\": [\n    {\n      \"pattern\": \"^/\",\n      \"replacement\": \"{{BASEURL}}/\"\n    }\n  ],\n  \"timeout\": \"120s\",\n  \"retryOn429\": true,\n  \"retryCount\": 5,\n  \"aliveStatusCodes\": [200, 206]\n}\n"
  },
  {
    "path": ".github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg",
    "content": "[DEFAULT]\ndebug: True\n\n[Nonce]\n# disable nonce check. THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nnonce_check_disable: False\n\n[Certificate]\nrevocation_reason_check_disable: False\n\n[Challenge]\n# when true disable challenge validation. Challenge will be set to 'valid' without further checking\n# THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nchallenge_validation_disable: False\n\n[Order]\ntnauthlist_support: False\nretry_after_timeout: 15\n\n[CAhandler]\n# CA specific options\nhandler_file: examples/ca_handler/openssl_ca_handler.py\nca_cert_chain_list: [\"volume/acme_ca/root-ca-cert.pem\"]\nissuing_ca_key: volume/acme_ca/sub-ca-key.pem\nissuing_ca_key_passphrase: Test1234\nissuing_ca_cert: volume/acme_ca/sub-ca-cert.pem\nissuing_ca_crl: volume/acme_ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_save_path: volume/acme_ca/certs\n"
  },
  {
    "path": ".github/openssl_ca_handler.py_acme_srv_default_handler.cfg",
    "content": "[DEFAULT]\ndebug: True\n\n[Nonce]\n# disable nonce check. THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nnonce_check_disable: False\n\n[Certificate]\nrevocation_reason_check_disable: False\n\n[Challenge]\n# when true disable challenge validation. Challenge will be set to 'valid' without further checking\n# THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nchallenge_validation_disable: False\n\n[Order]\ntnauthlist_support: False\nretry_after_timeout: 15\n\n[CAhandler]\n# CA specific options\nca_cert_chain_list: [\"volume/acme_ca/root-ca-cert.pem\"]\nissuing_ca_key: volume/acme_ca/sub-ca-key.pem\nissuing_ca_key_passphrase: Test1234\nissuing_ca_cert: volume/acme_ca/sub-ca-cert.pem\nissuing_ca_crl: volume/acme_ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_save_path: volume/acme_ca/certs\n"
  },
  {
    "path": ".github/openssl_ca_handler.py_acme_srv_default_handler_dns.cfg",
    "content": "[DEFAULT]\ndebug: True\n\n[Nonce]\n# disable nonce check. THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nnonce_check_disable: False\n\n[Certificate]\nrevocation_reason_check_disable: False\n\n[Challenge]\n# when true disable challenge validation. Challenge will be set to 'valid' without further checking\n# THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nchallenge_validation_disable: False\ndns_server_list: [\"DNS-IP\"]\n\n[Order]\ntnauthlist_support: False\nretry_after_timeout: 15\n\n[CAhandler]\n# CA specific options\nca_cert_chain_list: [\"volume/acme_ca/root-ca-cert.pem\"]\nissuing_ca_key: volume/acme_ca/sub-ca-key.pem\nissuing_ca_key_passphrase: Test1234\nissuing_ca_cert: volume/acme_ca/sub-ca-cert.pem\nissuing_ca_crl: volume/acme_ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_save_path: volume/acme_ca/certs\n"
  },
  {
    "path": ".github/openssl_ca_handler_v16.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"handler for an openssl ca\"\"\"\nfrom __future__ import print_function\nimport os\nimport json\nimport base64\nimport uuid\nimport re\nfrom OpenSSL import crypto\n\n# pylint: disable=E0401\nfrom acme.helper import (\n    load_config,\n    build_pem_file,\n    uts_now,\n    uts_to_date_utc,\n    b64_url_recode,\n    cert_serial_get,\n    convert_string_to_byte,\n    convert_byte_to_string,\n    csr_cn_get,\n    csr_san_get,\n)\n\n\nclass CAhandler(object):\n    \"\"\"CA  handler\"\"\"\n\n    def __init__(self, debug=None, logger=None):\n        self.debug = debug\n        self.logger = logger\n        self.issuer_dict = {\n            \"issuing_ca_key\": None,\n            \"issuing_ca_cert\": None,\n            \"issuing_ca_crl\": None,\n        }\n        self.ca_cert_chain_list = []\n        self.cert_validity_days = 365\n        self.openssl_conf = None\n        self.cert_save_path = None\n        self.save_cert_as_hex = False\n        self.whitelist = []\n        self.blacklist = []\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        if not self.issuer_dict[\"issuing_ca_key\"]:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _ca_load(self):\n        \"\"\"load ca key and cert\"\"\"\n        self.logger.debug(\"CAhandler._ca_load()\")\n        ca_key = None\n        ca_cert = None\n        # open key and cert\n        if \"issuing_ca_key\" in self.issuer_dict:\n            if os.path.exists(self.issuer_dict[\"issuing_ca_key\"]):\n                if \"passphrase\" in self.issuer_dict:\n                    with open(self.issuer_dict[\"issuing_ca_key\"], \"r\") as fso:\n                        ca_key = crypto.load_privatekey(\n                            crypto.FILETYPE_PEM,\n                            fso.read(),\n                            convert_string_to_byte(self.issuer_dict[\"passphrase\"]),\n                        )\n                else:\n                    with open(self.issuer_dict[\"issuing_ca_key\"], \"r\") as fso:\n                        ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, fso.read())\n        if \"issuing_ca_cert\" in self.issuer_dict:\n            if os.path.exists(self.issuer_dict[\"issuing_ca_cert\"]):\n                with open(self.issuer_dict[\"issuing_ca_cert\"], \"r\") as fso:\n                    ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, fso.read())\n        self.logger.debug(\"CAhandler._ca_load() ended\")\n        return (ca_key, ca_cert)\n\n    def _certificate_chain_verify(self, cert, ca_cert):\n        \"\"\"verify certificate chain\"\"\"\n        self.logger.debug(\"CAhandler._certificate_chain_verify()\")\n\n        error = None\n        pem_file = build_pem_file(\n            self.logger, None, b64_url_recode(self.logger, cert), True\n        )\n\n        try:\n            cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_file)\n        except BaseException as err_:\n            cert = None\n            error = err_\n\n        if not error:\n            # Create a certificate store and add ca cert(s)\n            try:\n                store = crypto.X509Store()\n                store.add_cert(ca_cert)\n            except BaseException:\n                error = \"issuing certificate could not be added to trust-store\"\n\n            if not error:\n                # add ca chain to truststore\n                for cert_name in self.ca_cert_chain_list:\n                    try:\n                        with open(cert_name, \"r\") as fso:\n                            cain_cert = crypto.load_certificate(\n                                crypto.FILETYPE_PEM, fso.read()\n                            )\n                        store.add_cert(cain_cert)\n                    except BaseException:\n                        error = (\n                            \"certificate {0} could not be added to trust store\".format(\n                                cert_name\n                            )\n                        )\n\n            if not error:\n                # Create a certificate context using the store and the downloaded certificate\n                store_ctx = crypto.X509StoreContext(store, cert)\n                # Verify the certificate, returns None if it can validate the certificate\n                try:\n                    # pylint: disable=E1111\n                    result = store_ctx.verify_certificate()\n                except BaseException as err_:\n                    result = str(err_)\n            else:\n                result = error\n        else:\n            result = \"certificate could not get parsed\"\n\n        self.logger.debug(\n            \"CAhandler._certificate_chain_verify() ended with {0}\".format(result)\n        )\n        return result\n\n    def _certificate_extensions_add(self, cert_extension_dic, cert, ca_cert):\n        \"\"\"verify certificate chain\"\"\"\n        self.logger.debug(\"CAhandler._certificate_extensions_add()\")\n\n        _tmp_list = []\n        # add extensins from config file\n        for extension in cert_extension_dic:\n            self.logger.debug(\n                \"adding extension: {0}: {1}: {2}\".format(\n                    extension,\n                    cert_extension_dic[extension][\"critical\"],\n                    cert_extension_dic[extension][\"value\"],\n                )\n            )\n            if extension == \"subjectKeyIdentifier\":\n                self.logger.info(\"Adding subjectKeyIdentifier extension\")\n                _tmp_list.append(\n                    crypto.X509Extension(\n                        convert_string_to_byte(extension),\n                        critical=cert_extension_dic[extension][\"critical\"],\n                        value=convert_string_to_byte(\n                            cert_extension_dic[extension][\"value\"]\n                        ),\n                        subject=cert,\n                    )\n                )\n            elif \"subject\" in cert_extension_dic[extension]:\n                self.logger.info(\"Adding subject extension\")\n                _tmp_list.append(\n                    crypto.X509Extension(\n                        convert_string_to_byte(extension),\n                        critical=cert_extension_dic[extension][\"critical\"],\n                        value=convert_string_to_byte(\n                            cert_extension_dic[extension][\"value\"]\n                        ),\n                        subject=cert,\n                    )\n                )\n            elif \"issuer\" in cert_extension_dic[extension]:\n                self.logger.info(\"Adding issuer\")\n                _tmp_list.append(\n                    crypto.X509Extension(\n                        convert_string_to_byte(extension),\n                        critical=cert_extension_dic[extension][\"critical\"],\n                        value=convert_string_to_byte(\n                            cert_extension_dic[extension][\"value\"]\n                        ),\n                        issuer=ca_cert,\n                    )\n                )\n            else:\n                _tmp_list.append(\n                    crypto.X509Extension(\n                        type_name=convert_string_to_byte(extension),\n                        critical=cert_extension_dic[extension][\"critical\"],\n                        value=convert_string_to_byte(\n                            cert_extension_dic[extension][\"value\"]\n                        ),\n                    )\n                )\n\n        self.logger.debug(\"CAhandler._certificate_extensions_add() ended\")\n        return _tmp_list\n\n    def _certificate_extensions_load(self):\n        \"\"\"verify certificate chain\"\"\"\n        self.logger.debug(\"CAhandler._certificate_extensions_load()\")\n\n        file_dic = dict(load_config(self.logger, None, self.openssl_conf))\n\n        cert_extention_dic = {}\n        if \"extensions\" in file_dic:\n            for extension in file_dic[\"extensions\"]:\n\n                cert_extention_dic[extension] = {}\n                parameters = file_dic[\"extensions\"][extension].split(\",\")\n\n                # set crititcal task if applicable\n                if parameters[0] == \"critical\":\n                    cert_extention_dic[extension][\"critical\"] = bool(parameters.pop(0))\n                else:\n                    cert_extention_dic[extension][\"critical\"] = False\n\n                # remove leading blank from first element\n                parameters[0] = parameters[0].lstrip()\n\n                # check if we have an issuer option (if so remove it and mark it as to be set)\n                if \"issuer:\" in parameters[-1]:\n                    cert_extention_dic[extension][\"issuer\"] = bool(parameters.pop(-1))\n\n                # check if we have an issuer option (if so remove it and mark it as to be set)\n                if \"subject:\" in parameters[-1]:\n                    cert_extention_dic[extension][\"subject\"] = bool(parameters.pop(-1))\n\n                # combine the remaining items and put them in as values\n                cert_extention_dic[extension][\"value\"] = \",\".join(parameters)\n\n        self.logger.debug(\"CAhandler._certificate_extensions_load() ended\")\n        return cert_extention_dic\n\n    def _certificate_store(self, cert):\n        \"\"\"store certificate on disk\"\"\"\n        self.logger.debug(\"CAhandler._certificate_store()\")\n        serial = cert.get_serial_number()\n        # save cert if needed\n        if self.cert_save_path and self.cert_save_path is not None:\n            # create cert-store dir if not existing\n            if not os.path.isdir(self.cert_save_path):\n                self.logger.debug(\"create certsavedir {0}\".format(self.cert_save_path))\n                os.mkdir(self.cert_save_path)\n\n            # determine filename\n            if self.save_cert_as_hex:\n                self.logger.info(\n                    \"Convert serial to hex: {0}: {1}\".format(\n                        serial, \"{:X}\".format(serial)\n                    )\n                )\n                cert_file = \"{:X}\".format(serial)\n            else:\n                cert_file = str(serial)\n            with open(\n                \"{0}/{1}.pem\".format(self.cert_save_path, cert_file), \"wb\"\n            ) as fso:\n                fso.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))\n        else:\n            self.logger.error(\n                \"CAhandler._certificate_store() handler configuration incomplete: cert_save_path is missing\"\n            )\n\n        self.logger.debug(\"CAhandler._certificate_store() ended\")\n\n    def _config_check(self):\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n        error = None\n        if \"issuing_ca_key\" in self.issuer_dict and self.issuer_dict[\"issuing_ca_key\"]:\n            if not os.path.exists(self.issuer_dict[\"issuing_ca_key\"]):\n                error = \"issuing_ca_key {0} does not exist\".format(\n                    self.issuer_dict[\"issuing_ca_key\"]\n                )\n        else:\n            error = \"issuing_ca_key not specfied in config_file\"\n\n        if not error:\n            if (\n                \"issuing_ca_cert\" in self.issuer_dict\n                and self.issuer_dict[\"issuing_ca_cert\"]\n            ):\n                if not os.path.exists(self.issuer_dict[\"issuing_ca_cert\"]):\n                    error = \"issuing_ca_cert {0} does not exist\".format(\n                        self.issuer_dict[\"issuing_ca_cert\"]\n                    )\n            else:\n                error = \"issuing_ca_cert must be specified in config file\"\n\n        if not error:\n            if (\n                \"issuing_ca_crl\" in self.issuer_dict\n                and self.issuer_dict[\"issuing_ca_crl\"]\n            ):\n                if not os.path.exists(self.issuer_dict[\"issuing_ca_crl\"]):\n                    error = \"issuing_ca_crl {0} does not exist\".format(\n                        self.issuer_dict[\"issuing_ca_crl\"]\n                    )\n            else:\n                error = \"issuing_ca_crl must be specified in config file\"\n\n        if not error:\n            if self.cert_save_path:\n                if not os.path.exists(self.cert_save_path):\n                    error = \"cert_save_path {0} does not exist\".format(\n                        self.cert_save_path\n                    )\n            else:\n                error = \"cert_save_path must be specified in config file\"\n\n        if not error:\n            if self.openssl_conf:\n                if not os.path.exists(self.openssl_conf):\n                    error = \"openssl_conf {0} does not exist\".format(self.openssl_conf)\n\n        if not error and not self.ca_cert_chain_list:\n            error = \"ca_cert_chain_list must be specified in config file\"\n\n        if error:\n            self.logger.error(\"CAhandler config error: {0}\".format(error))\n\n        self.logger.debug(\"CAhandler._config_check() ended\".format())\n        return error\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"issuing_ca_key\" in config_dic[\"CAhandler\"]:\n            self.issuer_dict[\"issuing_ca_key\"] = config_dic[\"CAhandler\"][\n                \"issuing_ca_key\"\n            ]\n        if \"issuing_ca_cert\" in config_dic[\"CAhandler\"]:\n            self.issuer_dict[\"issuing_ca_cert\"] = config_dic[\"CAhandler\"][\n                \"issuing_ca_cert\"\n            ]\n        if \"issuing_ca_key_passphrase_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.issuer_dict[\"passphrase\"] = os.environ[\n                    config_dic[\"CAhandler\"][\"issuing_ca_key_passphrase_variable\"]\n                ]\n            except BaseException as err:\n                self.logger.error(\n                    \"CAhandler._config_load() could not load issuing_ca_key_passphrase_variable:{0}\".format(\n                        err\n                    )\n                )\n        if \"issuing_ca_key_passphrase\" in config_dic[\"CAhandler\"]:\n            if \"passphrase\" in self.issuer_dict and self.issuer_dict[\"passphrase\"]:\n                self.logger.info(\"Overwrite issuing_ca_key_passphrase_variable\")\n            self.issuer_dict[\"passphrase\"] = config_dic[\"CAhandler\"][\n                \"issuing_ca_key_passphrase\"\n            ]\n        if \"ca_cert_chain_list\" in config_dic[\"CAhandler\"]:\n            self.ca_cert_chain_list = json.loads(\n                config_dic[\"CAhandler\"][\"ca_cert_chain_list\"]\n            )\n        if \"cert_validity_days\" in config_dic[\"CAhandler\"]:\n            self.cert_validity_days = int(config_dic[\"CAhandler\"][\"cert_validity_days\"])\n        if \"cert_save_path\" in config_dic[\"CAhandler\"]:\n            self.cert_save_path = config_dic[\"CAhandler\"][\"cert_save_path\"]\n        if \"issuing_ca_crl\" in config_dic[\"CAhandler\"]:\n            self.issuer_dict[\"issuing_ca_crl\"] = config_dic[\"CAhandler\"][\n                \"issuing_ca_crl\"\n            ]\n        # convert passphrase\n        if \"passphrase\" in self.issuer_dict:\n            self.issuer_dict[\"passphrase\"] = self.issuer_dict[\"passphrase\"].encode(\n                \"ascii\"\n            )\n        if \"openssl_conf\" in config_dic[\"CAhandler\"]:\n            self.openssl_conf = config_dic[\"CAhandler\"][\"openssl_conf\"]\n        if \"whitelist\" in config_dic[\"CAhandler\"]:\n            self.whitelist = json.loads(config_dic[\"CAhandler\"][\"whitelist\"])\n        if \"blacklist\" in config_dic[\"CAhandler\"]:\n            self.blacklist = json.loads(config_dic[\"CAhandler\"][\"blacklist\"])\n        self.save_cert_as_hex = config_dic.getboolean(\n            \"CAhandler\", \"save_cert_as_hex\", fallback=False\n        )\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _crl_check(self, crl, serial):\n        \"\"\"check if CRL already contains serial\"\"\"\n        self.logger.debug(\"CAhandler._crl_check()\")\n        sn_match = False\n\n        # convert to lower case\n        if isinstance(serial, str):\n            serial = serial.lower()\n\n        serial = convert_string_to_byte(serial)\n        if crl and serial:\n            crl_list = crl.get_revoked()\n            if crl_list:\n                for rev in crl_list:\n                    if serial == rev.get_serial().lower():\n                        sn_match = True\n                        break\n        self.logger.debug(\"CAhandler._crl_check() with:{0}\".format(sn_match))\n        return sn_match\n\n    def _csr_check(self, csr):\n        \"\"\"check CSR against definied whitelists\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        if self.whitelist or self.blacklist:\n            result = False\n            # get sans and build a list\n            _san_list = csr_san_get(self.logger, csr)\n\n            san_list = []\n            check_list = []\n\n            for san in _san_list:\n                try:\n                    # SAN list must be modified/filtered)\n                    (_san_type, san_value) = san.lower().split(\":\")\n                    san_list.append(san_value)\n                except BaseException:\n                    # force check to fail as something went wrong during parsing\n                    check_list.append(False)\n                    self.logger.debug(\n                        \"san_list parsing failed at entry: {0}\".format(san)\n                    )\n\n            # get common name and atttach it to san_list\n            cn_ = csr_cn_get(self.logger, csr)\n            if cn_:\n                cn_ = cn_.lower()\n                if cn_ not in san_list:\n                    # append cn to san_list\n                    self.logger.debug(\"append cn to san_list\")\n                    san_list.append(cn_)\n\n            # go over the san list and check each entry\n            for san in san_list:\n                check_list.append(\n                    self._string_wlbl_check(san, self.whitelist, self.blacklist)\n                )\n\n            if check_list:\n                # cover a cornercase with empty checklist (no san, no cn)\n                if False in check_list:\n                    result = False\n                else:\n                    result = True\n\n        else:\n            result = True\n\n        self.logger.debug(\"CAhandler._csr_check() ended with: {0}\".format(result))\n        return result\n\n    def _list_check(self, entry, list_, toggle=False):\n        \"\"\"check string against list\"\"\"\n        self.logger.debug(\"CAhandler._list_check({0}:{1})\".format(entry, toggle))\n        self.logger.debug(\"check against list: {0}\".format(list_))\n\n        # default setting\n        check_result = False\n\n        if entry:\n            if list_:\n                for regex in list_:\n                    regex_compiled = re.compile(regex)\n                    if bool(regex_compiled.search(entry)):\n                        # parameter is in set flag accordingly and stop loop\n                        check_result = True\n            else:\n                # empty list, flip parameter to make the check successful\n                check_result = True\n\n        if toggle:\n            # toggle result if this is a blacklist\n            check_result = not check_result\n\n        self.logger.debug(\n            \"CAhandler._list_check() ended with: {0}\".format(check_result)\n        )\n        return check_result\n\n    def _pemcertchain_generate(self, ee_cert, issuer_cert):\n        \"\"\"build pem chain\"\"\"\n        self.logger.debug(\"CAhandler._pemcertchain_generate()\")\n\n        if issuer_cert:\n            pem_chain = \"{0}{1}\".format(ee_cert, issuer_cert)\n        else:\n            pem_chain = ee_cert\n        for cert in self.ca_cert_chain_list:\n            if os.path.exists(cert):\n                with open(cert, \"r\") as fso:\n                    cert_pem = fso.read()\n                pem_chain = \"{0}{1}\".format(pem_chain, cert_pem)\n\n        self.logger.debug(\"CAhandler._pemcertchain_generate() ended\")\n        return pem_chain\n\n    def _string_wlbl_check(self, entry, white_list, black_list):\n        \"\"\"check single against whitelist and blacklist\"\"\"\n        self.logger.debug(\"CAhandler._string_wlbl_check({0})\".format(entry))\n\n        # default setting\n        chk_result = False\n\n        # check if entry is in white_list\n        wl_check = self._list_check(entry, white_list)\n        if wl_check:\n            self.logger.debug(\"{0} in white_list\".format(entry))\n            if black_list:\n                # we need to check blacklist if there is a blacklist and wl check passed\n                if self._list_check(entry, black_list):\n                    self.logger.debug(\"{0} in black_list\".format(entry))\n                else:\n                    self.logger.debug(\"{0} not in black_list\".format(entry))\n                    chk_result = True\n            else:\n                chk_result = wl_check\n        else:\n            self.logger.debug(\"{0} not in white_list\".format(entry))\n\n        self.logger.debug(\n            \"CAhandler._string_wlbl_check({0}) ended with: {1}\".format(\n                entry, chk_result\n            )\n        )\n        return chk_result\n\n    def enroll(self, csr):\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        cert_raw = None\n\n        error = self._config_check()\n\n        if not error:\n            try:\n                # check CN and SAN against black/whitlist\n                result = self._csr_check(csr)\n\n                if result:\n                    # prepare the CSR\n                    csr = build_pem_file(\n                        self.logger, None, b64_url_recode(self.logger, csr), None, True\n                    )\n\n                    # load ca cert and key\n                    (ca_key, ca_cert) = self._ca_load()\n\n                    # load certificate_profile (if applicable)\n                    if self.openssl_conf:\n                        cert_extension_dic = self._certificate_extensions_load()\n                    else:\n                        cert_extension_dic = []\n\n                    # creating a rest form CSR\n                    req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr)\n                    # sign csr\n                    cert = crypto.X509()\n                    cert.gmtime_adj_notBefore(0)\n                    cert.gmtime_adj_notAfter(self.cert_validity_days * 86400)\n                    cert.set_issuer(ca_cert.get_subject())\n                    cert.set_subject(req.get_subject())\n                    cert.set_pubkey(req.get_pubkey())\n                    cert.set_serial_number(uuid.uuid4().int)\n                    cert.set_version(2)\n                    cert.add_extensions(req.get_extensions())\n\n                    default_extension_list = [\n                        crypto.X509Extension(\n                            convert_string_to_byte(\"subjectKeyIdentifier\"),\n                            False,\n                            convert_string_to_byte(\"hash\"),\n                            subject=cert,\n                        ),\n                        crypto.X509Extension(\n                            convert_string_to_byte(\"authorityKeyIdentifier\"),\n                            False,\n                            convert_string_to_byte(\"keyid:always\"),\n                            issuer=ca_cert,\n                        ),\n                        crypto.X509Extension(\n                            convert_string_to_byte(\"basicConstraints\"),\n                            True,\n                            convert_string_to_byte(\"CA:FALSE\"),\n                        ),\n                        crypto.X509Extension(\n                            convert_string_to_byte(\"extendedKeyUsage\"),\n                            False,\n                            convert_string_to_byte(\"clientAuth,serverAuth\"),\n                        ),\n                    ]\n\n                    if cert_extension_dic:\n                        try:\n                            cert.add_extensions(\n                                self._certificate_extensions_add(\n                                    cert_extension_dic, cert, ca_cert\n                                )\n                            )\n                        except BaseException as err_:\n                            self.logger.error(\n                                \"CAhandler.enroll() error while loading extensions form file. Use default set.\\nerror: {0}\".format(\n                                    err_\n                                )\n                            )\n                            cert.add_extensions(default_extension_list)\n                    else:\n                        # add keyUsage if it does not exist in CSR\n                        ku_is_in = False\n                        for ext in req.get_extensions():\n                            if (\n                                convert_byte_to_string(ext.get_short_name())\n                                == \"keyUsage\"\n                            ):\n                                ku_is_in = True\n                        if not ku_is_in:\n                            default_extension_list.append(\n                                crypto.X509Extension(\n                                    convert_string_to_byte(\"keyUsage\"),\n                                    True,\n                                    convert_string_to_byte(\n                                        \"digitalSignature,keyEncipherment\"\n                                    ),\n                                )\n                            )\n\n                        # add default extensions\n                        cert.add_extensions(default_extension_list)\n\n                    cert.sign(ca_key, \"sha256\")\n\n                    # store certifiate\n                    self._certificate_store(cert)\n                    # create bundle and raw cert\n                    cert_bundle = self._pemcertchain_generate(\n                        convert_byte_to_string(\n                            crypto.dump_certificate(crypto.FILETYPE_PEM, cert)\n                        ),\n                        open(self.issuer_dict[\"issuing_ca_cert\"]).read(),\n                    )\n                    cert_raw = convert_byte_to_string(\n                        base64.b64encode(\n                            crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)\n                        )\n                    )\n                else:\n                    error = \"urn:ietf:params:acme:badCSR\"\n\n            except BaseException as err:\n                self.logger.error(\"CAhandler.enroll() error: {0}\".format(err))\n                error = \"Unknown exception\"\n\n        self.logger.debug(\"CAhandler.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def poll(self, _cert_name, poll_identifier, _csr):\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(self, cert, rev_reason=\"unspecified\", rev_date=None):\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke({0}: {1})\".format(rev_reason, rev_date))\n        code = None\n        message = None\n        detail = None\n\n        # overwrite revocation date - we ignore what has been submitted\n        rev_date = uts_to_date_utc(uts_now(), \"%y%m%d%H%M%SZ\")\n\n        if \"issuing_ca_crl\" in self.issuer_dict and self.issuer_dict[\"issuing_ca_crl\"]:\n            # load ca cert and key\n            (ca_key, ca_cert) = self._ca_load()\n            # turn of chain_check due to issues in pyopenssl (check is not working if key-usage is set)\n            # result = self._certificate_chain_verify(cert, ca_cert)\n            result = None\n            # proceed if the cert and ca-cert belong together\n            # if not result:\n            serial = cert_serial_get(self.logger, cert)\n            # serial = serial.replace('0x', '')\n            if ca_key and ca_cert and serial:\n                serial = hex(serial).replace(\"0x\", \"\")\n                if os.path.exists(self.issuer_dict[\"issuing_ca_crl\"]):\n                    # existing CRL\n                    with open(self.issuer_dict[\"issuing_ca_crl\"], \"r\") as fso:\n                        crl = crypto.load_crl(crypto.FILETYPE_PEM, fso.read())\n                    # check CRL already contains serial\n                    sn_match = self._crl_check(crl, serial)\n                else:\n                    # new CRL\n                    crl = crypto.CRL()\n                    sn_match = None\n\n                # this is the revocation operation\n                if not sn_match:\n                    revoked = crypto.Revoked()\n                    revoked.set_reason(convert_string_to_byte(rev_reason))\n                    revoked.set_serial(convert_string_to_byte(serial))\n                    revoked.set_rev_date(convert_string_to_byte(rev_date))\n                    crl.add_revoked(revoked)\n                    # save CRL\n                    crl_text = crl.export(\n                        ca_cert,\n                        ca_key,\n                        crypto.FILETYPE_PEM,\n                        7,\n                        convert_string_to_byte(\"sha256\"),\n                    )\n                    with open(self.issuer_dict[\"issuing_ca_crl\"], \"wb\") as fso:\n                        fso.write(crl_text)\n                    code = 200\n                else:\n                    code = 400\n                    message = \"urn:ietf:params:acme:error:alreadyRevoked\"\n                    detail = \"Certificate has already been revoked\"\n            else:\n                code = 400\n                message = \"urn:ietf:params:acme:error:serverInternal\"\n                detail = \"configuration error\"\n            # else:\n            #    code = 400\n            #    message = 'urn:ietf:params:acme:error:serverInternal'\n            #    detail = result\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:serverInternal\"\n            detail = \"Unsupported operation\"\n\n        self.logger.debug(\"CAhandler.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload):\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: {0}\".format(error))\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": ".github/pgpass",
    "content": "postgresdbsrv:*:*:postgres:foobar\n"
  },
  {
    "path": ".github/pycodestyle",
    "content": "[pycodestyle]\ncount = False\nignore = E501, W503, E203\nmax-line-length = 160\nstatistics = True\n"
  },
  {
    "path": ".github/pylintrc",
    "content": "# plyintrc for acme2certifier CI pipeline\n[MESSAGES CONTROL]\n# c0301 - line to long\n# r0205 - useless-object-inheritance\n# r0801 - Similar lines in 2 files\n# r0902 - too-many-instance-attributes\n# r0903 - too-few-public methods\n# r1702 - too many nested blocks\n# w0703 - too general exception\n# W1202 - logging-format-interpolation\n# W3101 - missing timeout for request calls\ndisable=C0301, R0205, R0801, R0902, R0903, R1702, W0703, W1202\n\n[DESIGN]\n# Maximum number of locals for function / method\nmax-locals=20\nmax-branches=20\nmax-public-methods=30\nmax-statements=100\n"
  },
  {
    "path": ".github/traefik-matrix.yml",
    "content": "services:\n  traefik:\n    image: traefik:latest\n    container_name: \"traefik\"\n    command:\n      - \"--log.level=DEBUG\"\n      - \"--api.insecure=true\"\n      - \"--providers.docker=true\"\n      - \"--providers.docker.exposedbydefault=false\"\n      - \"--entrypoints.web.address=:80\"\n      - \"--entrypoints.websecure.address=:443\"\n      - \"--certificatesresolvers.a2c.acme.CHALLENGE_TYPE\"\n      - \"--certificatesresolvers.a2c.acme.caserver=https://acme-srv.acme/directory\"\n      - \"--certificatesresolvers.a2c.acme.email=grindsa@foo.bar\"\n      - \"--certificatesresolvers.a2c.acme.storage=/letsencrypt/acme.json\"\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n      - \"8080:8080\"\n    environment:\n      - LEGO_CA_CERTIFICATES=/tmp/certs/acme2certifier_cabundle.pem\n\n    volumes:\n      - \"./certs:/tmp/certs\"\n      - \"./letsencrypt:/letsencrypt\"\n      - \"/var/run/docker.sock:/var/run/docker.sock\"\n\n  whoami:\n    image: traefik/whoami\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.whoami.rule=Host(`whoami.acme`)\"\n      - \"traefik.http.routers.whoami.entrypoints=web,websecure\"\n      - \"traefik.http.routers.whoami.tls.certresolver=a2c\"\n\nnetworks:\n  default:\n    name: acme\n    external: true\n"
  },
  {
    "path": ".github/workflows/app-acme-sh.yml",
    "content": "name: Application Tests - acme_sh\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        accountkeylength: [2048, ec-256, ec-521]\n        keylength: [2048, ec-521]\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/acme_sh/enroll\n        with:\n          KEYLENGTH: ${{ matrix.keylength }}\n          ACCOUNTKEYLENGTH: ${{ matrix.accountkeylength }}\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}-${{ matrix.accountkeylength }}_key-${{ matrix.keylength }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n        accountkeylength: [2048, ec-256, ec-521]\n        keylength: [2048, ec-521]\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        # if: matrix.execscript == 'rpm_tester.sh'\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/acme_sh/enroll\n        with:\n          KEYLENGTH: ${{ matrix.keylength }}\n          ACCOUNTKEYLENGTH: ${{ matrix.accountkeylength }}\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: alpn-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n        accountkeylength: [2048, ec-256, ec-521]\n        keylength: [2048, ec-521]\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/acme_sh/enroll\n        with:\n          KEYLENGTH: ${{ matrix.keylength }}\n          ACCOUNTKEYLENGTH: ${{ matrix.accountkeylength }}\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-caddy.yml",
    "content": "name: Application Tests - Caddy\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        ports: ['-p 80:80 -p 443:443', '-p 443:443']\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Create caddy folder and copy configuratation files\"\n        run: |\n          mkdir caddy\n          cp .github/Caddyfile caddy/\n          cp .github/acme2certifier_cabundle.pem caddy\n\n      - name: \"Enroll certificate with Caddy\"\n        run: |\n          docker run -d --rm  ${{ matrix.ports }} --network acme -v $PWD/caddy/Caddyfile:/etc/caddy/Caddyfile -v$PWD/caddy/acme2certifier_cabundle.pem:/tmp/acme2certifier_cabundle.pem -v $(pwd)/caddy/config:/config -v $(pwd)/caddy/data:/data --name=caddy caddy:2\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check for logs indicating successful enrollment\"\n        run: |\n          docker logs caddy 2>&1 | grep \"successfully downloaded available certificate chains\"\n          docker logs caddy 2>&1 | grep \"certificate obtained successfully\"\n          docker logs caddy 2>&1 | grep \"got renewal info\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp caddy/ ${{ github.workspace }}/artifact/caddy/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/a2c.log\n          docker logs caddy 2> ${{ github.workspace }}/artifact/caddy.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log caddy.log data caddy\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ github.run_id }}.${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-certbot.yml",
    "content": "name: Application Tests - Certbot\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        keylength: [2048, 4096]\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Create letsencrypt folder\"\n        run: |\n          mkdir certbot\n\n      - name: \"Register certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"Enroll HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --rsa-key-size ${{ matrix.keylength }} --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Renew HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --rsa-key-size ${{ matrix.keylength }} --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot --force-renewal\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Revoke HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n\n      - name: \"Enroll HTTP-01 2x domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --rsa-key-size ${{ matrix.keylength }} --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme -d certbot. --cert-name certbot\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Renew HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --rsa-key-size ${{ matrix.keylength }} --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme -d certbot. --cert-name certbot --force-renewal\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Revoke HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme -d certbot. --cert-name certbot\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data certbot\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.keylength }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-certmanager.yml",
    "content": "name: Application Tests - cert-manager\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images using http01 challenges\n  # ---------------------------------------------------------\n  http01-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install microk8s\"\n        run: |\n          sudo snap install microk8s --classic\n          sudo microk8s status --wait-ready\n          sudo microk8s enable helm3\n          sudo microk8s enable ingress\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo mkdir -p data\n          sudo cp .github/dnsmasq.conf data\n          sudo cp .github/dnsmasq.yml data\n          sudo chmod -R 777 data/dnsmasq.conf\n          sudo chmod -R 777 data/dnsmasq.yml\n          sudo sed -i \"s/RUNNER_IP/${{ env.RUNNER_IP }}/g\" data/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_PATH/${{ env.RUNNER_PATH }}/g\" data/dnsmasq.yml\n          cat data/dnsmasq.conf\n          cat data/dnsmasq.yml\n          docker pull gigantuar/dnsmasq:latest-amd64\n          docker save gigantuar/dnsmasq -o dnsmasq.tar\n          sudo microk8s ctr image import dnsmasq.tar\n          sudo microk8s ctr images ls | grep -i gigantuar\n\n      - name: \"Deploy dnsmasq pod\"\n        run: |\n          sudo microk8s.kubectl apply -f data/dnsmasq.yml\n\n      - name: \"[ WAIT ] Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check status dnsmasq pod and grab ip\"\n        run: |\n          sudo microk8s.kubectl get pods -n dnsmasq\n          sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq\n          sudo microk8s.kubectl get pods -n dnsmasq | grep -i Running\n          sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq | grep \" IP:\" | cut -d ' ' -f 5\n          echo DNSMASQ_IP=$(sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq | grep \" IP:\" | cut -d ' ' -f 5) >> $GITHUB_ENV\n      - run: echo \"dnsmasq pod IP is ${{ env.DNSMASQ_IP }}\"\n\n      - name: \"Change and test dns\"\n        run: |\n          sudo cp .github/k8s-acme-srv.yml data/\n          sudo chmod 777 data/k8s-acme-srv.yml\n          sudo sed -i \"s/DNSMASQ_IP/${{ env.DNSMASQ_IP }}/g\" data/k8s-acme-srv.yml\n          cat data/k8s-acme-srv.yml\n          host www.bar.local ${{ env.DNSMASQ_IP }}\n\n      - name: \"Install cert-manager charts\"\n        run: |\n          sudo microk8s.kubectl create namespace cert-manager\n          sudo microk8s.helm3 repo add jetstack https://charts.jetstack.io\n          sudo microk8s.helm3 repo update\n          sudo microk8s.helm3 install cert-manager jetstack/cert-manager --namespace cert-manager  --set crds.enabled=true  --set podDnsPolicy=\"None\",podDnsConfig.nameservers={${{ env.DNSMASQ_IP }}}\n          echo CERTMGR_VERSION=$(sudo microk8s.helm3 show chart jetstack/cert-manager | grep version) >> $GITHUB_ENV\n      - run: echo \"cert-manager ${{ env.CERTMGR_VERSION }}\"\n\n      - name: \"Import container to k8s\"\n        run: |\n          docker save acme2certifier/$DB_HANDLER > a2c.tar\n          sudo microk8s ctr image import a2c.tar\n          sudo microk8s ctr images ls | grep -i acme2certifier\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Create a2c configuration\"\n        run: |\n          sudo mkdir -p data\n          sudo cp .github/acme2certifier.pem data/acme2certifier.pem\n          sudo cp .github/acme2certifier_cert.pem data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/acme2certifier_key.pem\n          sudo cp .github/django_settings.py data/settings.py\n          sudo cp examples/ca_handler/openssl_ca_handler.py data/ca_handler.py\n          sudo mkdir -p data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/acme_srv.cfg\n          sudo chmod 777 data/acme_srv.cfg\n\n      - name: \"Deploy a2c pod\"\n        run: |\n          sudo sed -i \"s/grindsa\\/acme2certifier\\:devel/docker.io\\/acme2certifier\\/$DB_HANDLER:latest/g\" data/k8s-acme-srv.yml\n          sudo microk8s.kubectl apply -f data/k8s-acme-srv.yml\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n        env:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check status a2c pod and grab ip of a2c pod\"\n        run: |\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier\n          sudo microk8s.kubectl get pods -n cert-manager-acme | grep -i Running\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5\n          echo ACME_IP=$(sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5) >> $GITHUB_ENV\n      - run: echo \"a2c pod IP is ${{ env.ACME_IP }}\"\n\n      - name: \"Deploy cert-manager and trigger enrollment\"\n        run: |\n          sudo cp .github/k8s-cert-mgr-http-01.yml data\n          sudo chmod -R 777 data/k8s-cert-mgr-http-01.yml\n          sudo sed -i \"s/ACME_SRV/${{ env.ACME_IP }}/g\" data/k8s-cert-mgr-http-01.yml\n          sudo sed -i \"s/k8.acme.dynamop.de/k8.${{ matrix.websrv }}-${{ matrix.dbhandler }}.acme.dynamop.de/g\" data/k8s-cert-mgr-http-01.yml\n          sudo microk8s.kubectl apply -f data/k8s-cert-mgr-http-01.yml\n\n      - name: \"Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n\n      - name: \"Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n      - name: \"[ WAIT ] Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n          sudo microk8s.kubectl describe certificate\n          sudo microk8s.kubectl describe certificates | grep -i \"The certificate has been successfully issued\"\n      - name: \"[ * ] Collecting test logs\"\n\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo microk8s.kubectl logs acme2certifier -n cert-manager-acme > ${{ github.workspace }}/artifact/acme2certifier.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme2certifier.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: http01-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images using dns01 challenges\n  # ---------------------------------------------------------\n  dns01-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        # apache throws errors for some reason\n        websrv: ['nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CF_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Change dns\"\n        run: |\n          sudo mkdir -p data\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          sudo chmod -R 777 /etc/resolv.conf\n          sudo echo \"nameserver 1.1.1.1\" > /etc/resolv.conf\n          sudo cat /etc/resolv.conf\n          sudo cp .github/k8s-acme-srv.yml data/\n          sudo chmod 777 data/k8s-acme-srv.yml\n          sudo sed -i \"s/DNSMASQ_IP/1.1.1.1/g\" data/k8s-acme-srv.yml\n          cat data/k8s-acme-srv.yml\n\n      - name: \"Install microk8s\"\n        run: |\n          sudo snap install microk8s --classic\n          sudo microk8s status --wait-ready\n          sudo microk8s enable helm3\n\n      - name: \"Install cert-manager charts\"\n        run: |\n          sudo microk8s.kubectl create namespace cert-manager\n          sudo microk8s.helm3 repo add jetstack https://charts.jetstack.io\n          sudo microk8s.helm3 repo update\n          sudo microk8s.helm3 install \\\n            cert-manager jetstack/cert-manager \\\n            --namespace cert-manager \\\n            --set crds.enabled=true\n          echo CERTMGR_VERSION=$(sudo microk8s.helm3 show chart jetstack/cert-manager | grep version) >> $GITHUB_ENV\n\n      - run: echo \"cert-manager ${{ env.CERTMGR_VERSION }}\"\n\n      - name: \"Import container to k8s\"\n        run: |\n          docker save acme2certifier/$DB_HANDLER > a2c.tar\n          sudo microk8s ctr image import a2c.tar\n          sudo microk8s ctr images ls | grep -i acme2certifier\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Create a2c configuration\"\n        run: |\n          sudo mkdir -p data\n          sudo cp .github/acme2certifier.pem data/acme2certifier.pem\n          sudo cp .github/acme2certifier_cert.pem data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/acme2certifier_key.pem\n          sudo cp .github/django_settings.py data/settings.py\n          sudo cp examples/ca_handler/openssl_ca_handler.py data/ca_handler.py\n          sudo mkdir -p data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/acme_srv.cfg\n          sudo chmod 777 data/acme_srv.cfg\n\n      - name: \"Deploy a2c pod\"\n        run: |\n          sudo sed -i \"s/grindsa\\/acme2certifier\\:devel/docker.io\\/acme2certifier\\/$DB_HANDLER:latest/g\" data/k8s-acme-srv.yml\n          sudo microk8s.kubectl apply -f data/k8s-acme-srv.yml\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n        env:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n\n      - name: \"[ WAIT ] Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check status a2c pod and grab ip of a2c pod\"\n        run: |\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier\n          sudo microk8s.kubectl get pods -n cert-manager-acme | grep -i Running\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5\n          echo ACME_IP=$(sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5) >> $GITHUB_ENV\n\n      - run: echo \"a2c pod IP is $ACME_IP\"\n        env:\n          ACME_IP: ${{ env.ACME_IP }}\n\n      - name: \"Deploy cert-manager\"\n        run: |\n          sudo cp .github/k8s-cert-mgr-dns-01.yml data\n          sudo chmod -R 777 data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/ACME_SRV/$ACME_IP/g\" data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/CF_TOKEN/$CF_TOKEN/g\" data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/MY_EMAIL/$EMAIL/g\" data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/k8.acme.dynamop.de/k8.$WEB_SRV-$DB_HANDLER.acme.dynamop.de/g\" data/k8s-cert-mgr-dns-01.yml\n        env:\n          ACME_IP: ${{ env.ACME_IP }}\n          EMAIL: ${{ secrets.EMAIL }}\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Deploy cert-manager and trigger enrollment\"\n        run: |\n          sudo microk8s.kubectl apply -f data/k8s-cert-mgr-dns-01.yml\n\n      - name: \"Sleep for 45s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 45s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n\n      - name: \"Sleep for 30s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n\n      - name: \"Sleep for 60s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n\n      - name: \"Sleep for 60s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check challenge and certificate\"\n        run: |\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n          sudo microk8s.kubectl describe certificates acme-cert -n cert-manager-acme\n          sudo microk8s.kubectl describe certificates acme-cert -n cert-manager-acme | grep -i \"The certificate has been successfully issued\"\n\n      - name: \"Reconfigure YAML to wildcard domain\"\n        run: |\n          sudo microk8s.kubectl delete -f data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/commonName: k8.acme.dynamop.de/commonName: '*.acme.dynamop.de'/g\" data/k8s-cert-mgr-dns-01.yml\n          sudo sed -i \"s/- k8.$WEB_SRV-$DB_HANDLER.acme.dynamop.de/- k8.$WEB_SRV-$DB_HANDLER.acme.dynamop.de\\n    - '*.$WEB_SRV-$DB_HANDLER.acme.dynamop.de'/g\" data/k8s-cert-mgr-dns-01.yml\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Deploy cert-manager and trigger enrollment\"\n        run: |\n          sudo microk8s.kubectl apply -f data/k8s-cert-mgr-dns-01.yml\n\n      - name: \"Sleep for 60s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n      - name: \"[ WAIT ] Sleep for 30s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n\n      - name: \"Sleep for 60s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe issuer acme2certifier -n cert-manager-acme\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n\n      - name: \"Sleep for 60s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 60s\n\n      - name: \"Check challenge and certificate\"\n        run: |\n          sudo microk8s.kubectl describe challenge -n cert-manager-acme\n          sudo microk8s.kubectl describe certificates acme-cert -n cert-manager-acme\n          sudo microk8s.kubectl describe certificates acme-cert -n cert-manager-acme | grep -i \"The certificate has been successfully issued\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo microk8s.kubectl logs acme2certifier -n cert-manager-acme > ${{ github.workspace }}/artifact/acme2certifier.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme2certifier.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: dns01-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images with eab\n  # ---------------------------------------------------------\n  eab-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install microk8s\"\n        run: |\n          sudo snap install microk8s --classic\n          sudo microk8s status --wait-ready\n          sudo microk8s enable helm3\n          sudo microk8s enable ingress\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo mkdir -p data\n          sudo cp .github/dnsmasq.conf data\n          sudo cp .github/dnsmasq.yml data\n          sudo chmod -R 777 data/dnsmasq.conf\n          sudo chmod -R 777 data/dnsmasq.yml\n          sudo sed -i \"s/RUNNER_IP/${{ env.RUNNER_IP }}/g\" data/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_PATH/${{ env.RUNNER_PATH }}/g\" data/dnsmasq.yml\n          cat data/dnsmasq.conf\n          cat data/dnsmasq.yml\n          docker pull gigantuar/dnsmasq:latest-amd64\n          docker save gigantuar/dnsmasq -o dnsmasq.tar\n          sudo microk8s ctr image import dnsmasq.tar\n          sudo microk8s ctr images ls | grep -i gigantuar\n\n      - name: \"Deploy dnsmasq pod\"\n        run: |\n          sudo microk8s.kubectl apply -f data/dnsmasq.yml\n\n      - name: \"[ WAIT ] Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check status dnsmasq pod and grab ip\"\n        run: |\n          sudo microk8s.kubectl get pods -n dnsmasq\n          sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq\n          sudo microk8s.kubectl get pods -n dnsmasq | grep -i Running\n          sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq | grep \" IP:\" | cut -d ' ' -f 5\n          echo DNSMASQ_IP=$(sudo microk8s.kubectl -n dnsmasq describe pod dnsmasq | grep \" IP:\" | cut -d ' ' -f 5) >> $GITHUB_ENV\n      - run: echo \"dnsmasq pod IP is ${{ env.DNSMASQ_IP }}\"\n\n      - name: \"Change and test dns\"\n        run: |\n          sudo cp .github/k8s-acme-srv.yml data/\n          sudo chmod 777 data/k8s-acme-srv.yml\n          sudo sed -i \"s/DNSMASQ_IP/${{ env.DNSMASQ_IP }}/g\" data/k8s-acme-srv.yml\n          cat data/k8s-acme-srv.yml\n          host www.bar.local ${{ env.DNSMASQ_IP }}\n\n      - name: \"Install cert-manager charts\"\n        run: |\n          sudo microk8s.kubectl create namespace cert-manager\n          sudo microk8s.helm3 repo add jetstack https://charts.jetstack.io\n          sudo microk8s.helm3 repo update\n          sudo microk8s.helm3 install cert-manager jetstack/cert-manager --namespace cert-manager  --set crds.enabled=true  --set podDnsPolicy=\"None\",podDnsConfig.nameservers={${{ env.DNSMASQ_IP }}}\n          echo CERTMGR_VERSION=$(sudo microk8s.helm3 show chart jetstack/cert-manager | grep version) >> $GITHUB_ENV\n      - run: echo \"cert-manager ${{ env.CERTMGR_VERSION }}\"\n\n      - name: \"Import container to k8s\"\n        run: |\n          docker save acme2certifier/$DB_HANDLER > a2c.tar\n          sudo microk8s ctr image import a2c.tar\n          sudo microk8s ctr images ls | grep -i acme2certifier\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Create a2c configuration\"\n        run: |\n          sudo mkdir -p data\n          sudo cp .github/acme2certifier.pem data/acme2certifier.pem\n          sudo cp .github/acme2certifier_cert.pem data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/acme2certifier_key.pem\n          sudo cp .github/django_settings.py data/settings.py\n          sudo cp examples/ca_handler/openssl_ca_handler.py data/ca_handler.py\n          sudo mkdir -p data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/acme_srv.cfg\n          sudo chmod 777 data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/json_handler.py\" >> data/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/key_file.json\" >> data/acme_srv.cfg\n          sudo cp examples/eab_handler/key_file.json data/acme_ca/\n\n      - name: \"Deploy a2c pod\"\n        run: |\n          sudo sed -i \"s/grindsa\\/acme2certifier\\:devel/docker.io\\/acme2certifier\\/$DB_HANDLER:latest/g\" data/k8s-acme-srv.yml\n          sudo microk8s.kubectl apply -f data/k8s-acme-srv.yml\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n        env:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Check status a2c pod and grab ip of a2c pod\"\n        run: |\n          sudo microk8s.kubectl get pods -n cert-manager-acme\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier\n          sudo microk8s.kubectl get pods -n cert-manager-acme | grep -i Running\n          sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5\n          echo ACME_IP=$(sudo microk8s.kubectl -n cert-manager-acme describe pod acme2certifier | grep \" IP:\" | cut -d ' ' -f 5) >> $GITHUB_ENV\n      - run: echo \"a2c pod IP is ${{ env.ACME_IP }}\"\n\n      - name: \"Create secret for eab\"\n        run: |\n          sudo microk8s.kubectl -n cert-manager create secret generic eab-secret --from-literal secret=V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\n          sudo microk8s.kubectl -n cert-manager get secret eab-secret -o yaml\n          sudo microk8s.kubectl -n cert-manager describe secret eab-secret\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Deploy cert-manager and trigger enrollment\"\n        run: |\n          sudo cp .github/k8s-cert-mgr-http-01.yml data\n          sudo chmod -R 777 data/k8s-cert-mgr-http-01.yml\n          sudo sed -i \"s/ACME_SRV/${{ env.ACME_IP }}/g\" data/k8s-cert-mgr-http-01.yml\n          sudo sed -i \"s/k8.acme.dynamop.de/k8.${{ matrix.websrv }}-${{ matrix.dbhandler }}.acme.dynamop.de/g\" data/k8s-cert-mgr-http-01.yml\n          sudo sed -i \"s/privateKeySecretRef:/externalAccountBinding:\\n      keyID: keyid_00\\n      keySecretRef:\\n        name: eab-secret\\n        key: secret\\n    privateKeySecretRef:/g\" data/k8s-cert-mgr-http-01.yml\n          cat data/k8s-cert-mgr-http-01.yml\n          sudo microk8s.kubectl apply -f data/k8s-cert-mgr-http-01.yml\n\n      - name: \"Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n\n      - name: \"Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n      - name: \"[ WAIT ] Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Check issuer and challenge\"\n        run: |\n          sudo microk8s.kubectl describe ClusterIssuer acme2certifier\n          sudo microk8s.kubectl describe challenge\n          sudo microk8s.kubectl describe certificate\n          sudo microk8s.kubectl describe certificates | grep -i \"The certificate has been successfully issued\"\n      - name: \"[ * ] Collecting test logs\"\n\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo microk8s.kubectl logs acme2certifier -n cert-manager-acme > ${{ github.workspace }}/artifact/acme2certifier.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme2certifier.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: eab-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-lego.yml",
    "content": "name: Application Tests - lego\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        keylength: [rsa2048, rsa4096, ec256]\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"create folders\"\n        run: |\n          mkdir lego\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http renew --no-random-sleep\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"Enroll HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http run\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http renew --no-random-sleep\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: tests-containers-${{ matrix.keylength }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        keylength: [rsa2048, rsa4096, ec256]\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http renew --no-random-sleep\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"Enroll HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http run\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http renew --no-random-sleep\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        keylength: [rsa2048, rsa4096, ec256]\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme --http renew --no-random-sleep\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 single domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"Enroll HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http run\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Renew HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme -d lego --http renew --no-random-sleep\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke HTTP-01 2x domain lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --key-type ${{ matrix.keylength }} --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-traeffik.yml",
    "content": "name: Application Tests - Traefik\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n        challenge_type: [tlschallenge=true, httpchallenge.entrypoint=web]\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"get runner information\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_HOSTNAME=$(hostname -f) >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - run: echo \"runner hostname is ${{ env.RUNNER_HOSTNAME }}\"\n        env:\n          RUNNER_HOSTNAME: ${{ env.RUNNER_HOSTNAME }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Setup and instanciate traefik\"\n        run: |\n          mkdir -p traefik/certs/\n          sudo cp .github/acme2certifier_cabundle.pem traefik/certs/\n          sudo cp .github/traefik-matrix.yml traefik/docker-compose.yml\n          sudo sed -i \"s/whoami.acme/${{ env.RUNNER_HOSTNAME }}/g\" traefik/docker-compose.yml\n          sudo sed -i \"s/CHALLENGE_TYPE/${{ matrix.challenge_type }}/g\" traefik/docker-compose.yml\n          cd traefik\n          docker compose up -d\n\n      - name: \"Sleep for 30s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 30s\n\n      - name: \"Check for certificate\"\n        working-directory: traefik\n        run: |\n          sudo cat letsencrypt/acme.json | jq -r '.a2c | .Certificates | . [] | .certificate ' | base64 -d |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          openssl verify -CAfile cert-3.pem -untrusted cert-2.pem cert-1.pem\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker logs traefik > traefik/traefik.log\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp traefik/ ${{ github.workspace }}/artifact/traefik/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data traefik\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.challenge_type }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n        challenge_type: [tlschallenge=true, httpchallenge.entrypoint=web]\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"get runner information\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_HOSTNAME=$(hostname -f) >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - run: echo \"runner hostname is ${{ env.RUNNER_HOSTNAME }}\"\n        env:\n          RUNNER_HOSTNAME: ${{ env.RUNNER_HOSTNAME }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Setup and instanciate traefik\"\n        run: |\n          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n          echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n          sudo apt update\n          sudo apt install -y docker-compose-plugin\n          mkdir -p traefik/certs/\n          sudo cp .github/acme2certifier_cabundle.pem traefik/certs/\n          sudo cp .github/traefik-matrix.yml traefik/docker-compose.yml\n          sudo sed -i \"s/whoami.acme/${{ env.RUNNER_HOSTNAME }}/g\" traefik/docker-compose.yml\n          sudo sed -i \"s/CHALLENGE_TYPE/${{ matrix.challenge_type }}/g\" traefik/docker-compose.yml\n          cd traefik\n          docker compose up -d\n\n      - name: \"Sleep for 30s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 30s\n\n      - name: \"Check for certificate\"\n        working-directory: traefik\n        run: |\n          sudo cat letsencrypt/acme.json | jq -r '.a2c | .Certificates | . [] | .certificate ' | base64 -d |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          openssl verify -CAfile cert-3.pem -untrusted cert-2.pem cert-1.pem\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        continue-on-error: true\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          mkdir -p ${{ github.workspace }}/artifact/clients\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp *.pem ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/clients/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/clients/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/clients/lego/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data clients acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpms-rh${{ matrix.rhversion }}-${{ matrix.execscript}}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n        challenge_type: [tlschallenge=true, httpchallenge.entrypoint=web]\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"get runner information\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_HOSTNAME=$(hostname -f) >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - run: echo \"runner hostname is ${{ env.RUNNER_HOSTNAME }}\"\n        env:\n          RUNNER_HOSTNAME: ${{ env.RUNNER_HOSTNAME }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Setup and instanciate traefik\"\n        run: |\n          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n          echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n          sudo apt update\n          sudo apt install -y docker-compose-plugin\n          mkdir -p traefik/certs/\n          sudo cp .github/acme2certifier_cabundle.pem traefik/certs/\n          sudo cp .github/traefik-matrix.yml traefik/docker-compose.yml\n          sudo sed -i \"s/whoami.acme/${{ env.RUNNER_HOSTNAME }}/g\" traefik/docker-compose.yml\n          sudo sed -i \"s/CHALLENGE_TYPE/${{ matrix.challenge_type }}/g\" traefik/docker-compose.yml\n          cd traefik\n          docker compose up -d\n\n      - name: \"Sleep for 30s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 30s\n\n      - name: \"Check for certificate\"\n        working-directory: traefik\n        run: |\n          sudo cat letsencrypt/acme.json | jq -r '.a2c | .Certificates | . [] | .certificate ' | base64 -d |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          openssl verify -CAfile cert-3.pem -untrusted cert-2.pem cert-1.pem\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/app-winacme.yml",
    "content": "name: Application Tests - win-acme\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  win_acme:\n    name: \"win_acme\"\n    runs-on: windows-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret - CF_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CF_CFG }}\n\n      - name: \"Get RunnerIP\"\n        run: |\n          Get-NetIPAddress -AddressFamily IPv4\n          # $runner_ip=(Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias 'Ethernet').IPAddress\n          $runner_ip=(Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias 'vEthernet (nat)').IPAddress\n          echo RUNNER_IP=$runner_ip >> $env:GITHUB_ENV\n\n      - name: \"Echo RunnerIP\"\n        run:  echo $env:RUNNER_IP\n\n      - name: \"[ PREPARE ] Create DNS entries \"\n        run: |\n          Invoke-RestMethod -ContentType \"application/json\" -Method PUT -Uri \"${{ env.CF_DYNAMOP_URL }}\" -Headers @{Authorization=\"Bearer ${{ env.CF_TOKEN }}\" } -UseBasicParsing -Body '{\"type\":\"A\",\"name\":\"${{ env.CF_WINACME1_NAME }}\",\"content\":\"${{ env.RUNNER_IP }}\",\"ttl\":120,\"proxied\":false}'\n        shell: pwsh\n        env:\n          CF_DYNAMOP_URL: ${{ env.CF_DYNAMOP_URL }}\n          CF_TOKEN: ${{ env.CF_TOKEN }}\n          CF_WINACME1_NAME: ${{ env.CF_WINACME1_NAME }}\n          CF_WINACME2_NAME: ${{ env.CF_WINACME2_NAME }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n        with:\n          OS: \"Windows\"\n\n      - name: \"Build local acme2certifier environment\"\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements.txt\n          pip install django\n          pip install django-sslserver-v2\n          pip install pyyaml\n          cp examples/db_handler/django_handler.py acme_srv/db_handler.py\n          cp examples/django/* .\\ -Recurse -Force\n          (Get-Content .github/django_settings.py) -replace '/var/www/acme2certifier/volume/db.sqlite3', 'volume/db.sqlite3' | Set-Content acme2certifier/settings.py\n          (Get-Content acme2certifier/settings.py) -replace 'django.contrib.staticfiles', 'sslserver' | Set-Content acme2certifier/settings.py\n          cp examples/ca_handler/openssl_ca_handler.py acme2certifier/ca_handler.py\n          cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg acme_srv/acme_srv.cfg\n          cp .github/acme2certifier_cert.pem acme2certifier/acme2certifier_cert.pem\n          cp .github/acme2certifier_key.pem acme2certifier/acme2certifier_key.pem\n          mkdir .\\volume/acme_ca/certs\n          cp test/ca/*.pem volume/acme_ca/\n          certutil  -addstore -enterprise -f -v root volume\\acme_ca\\root-ca-cert.pem\n          certutil  -addstore -enterprise -f -v root volume\\acme_ca\\sub-ca-cert.pem\n\n      - name: \"Configure server\"\n        run: |\n          python manage.py makemigrations\n          python manage.py migrate\n          python manage.py loaddata acme_srv/fixture/status.yaml\n\n      - name: \"Try to get up the server\"\n        run: |\n          Start-Process powershell {python .\\manage.py runserver 0.0.0.0:8080 3>&1 2>&1 > volume\\redirection.log}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test if directory ressource is accessible\"\n        run: |\n          get-Process python\n          Invoke-RestMethod -Uri http://127.0.0.1:8080/directory -NoProxy -TimeoutSec 5\n          [System.Net.Dns]::GetHostByName('localhost').HostName\n          ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname\n\n      - name: \"Download win-acme\"\n        run: |\n          Invoke-RestMethod -Uri https://github.com/win-acme/win-acme/releases/download/v2.2.8.1635/win-acme.v2.2.8.1635.x64.trimmed.zip -OutFile win-acme.zip\n          Expand-Archive .\\win-acme.zip\n          mkdir win-acme\\certs\n          dir win-acme\\*\n\n      - name: \"Enroll certificate via win-acme\"\n        run: |\n          .\\win-acme\\wacs.exe --baseuri http://127.0.0.1:8080 --emailaddress=grindsa@bar.local --pemfilespath win-acme\\certs --source manual --host \"%CF_WINACME1_NAME%\",\"%CF_WINACME2_NAME%\" --store pemfiles --force\n        shell: cmd\n        env:\n          CF_WINACME1_NAME: ${{ env.CF_WINACME1_NAME }}\n          CF_WINACME2_NAME: ${{ env.CF_WINACME2_NAME }}\n\n      - name: \"Try to get up the sslserver\"\n        run: |\n          Start-Process powershell {python .\\manage.py runsslserver 0.0.0.0:443 --certificate acme2certifier/acme2certifier_cert.pem --key acme2certifier/acme2certifier_key.pem 3>&1 2>&1 > volume\\redirection_ssl.log}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test if directory ressource is accessible\"\n        run: |\n          get-Process python\n          Invoke-RestMethod -SkipCertificateCheck -Uri https://localhost -NoProxy -TimeoutSec 5\n          [System.Net.Dns]::GetHostByName('localhost').HostName\n          ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname\n\n      - name: \"Install and configure Posh-ACME\"\n        run: |\n          Install-Module -Name Posh-ACME -Scope CurrentUser -Force\n\n      - name: \"Create account via Posh-ACME\"\n        run: |\n          set-PAServer -DirectoryUrl https://localhost/directory -SkipCertificateCheck\n          $DebugPreference = 'Continue'\n          New-PAAccount -Contact 'foo@bar.local'\n          $ACC_1 = (Get-PAAccount | Out-String -Stream | Select-String -Pattern \"valid\")\n          echo ACC1=$ACC_1 >> $env:GITHUB_ENV\n          Export-PAAccountKEy -OutputFile foo.key\n\n      - name: \"Recreate account via Posh-ACME\"\n        run: |\n          $DebugPreference = 'Continue'\n          Get-PAAccount | Remove-PAAccount -Force\n          Get-PAAccount\n          New-PAAccount -Contact 'win4@bar.local' -AcceptTOS -OnlyReturnExisting  -KeyFile foo.key\n          Get-PAAccount -Refresh\n          $ACC_2 = (Get-PAAccount | Out-String -Stream | Select-String -Pattern \"valid\")\n          echo ACC2=$ACC_2 >> $env:GITHUB_ENV\n          echo $env:ACC_2\n\n      - name: \"Rollover account key\"\n        run: |\n          $DebugPreference = 'Continue'\n          Set-PAAccount -KeyRollover\n\n      - name: \"Enroll Certificate via Posh-ACME\"\n        # if: $env:ACC_1 == env.ACC_2\n        run: |\n          $DebugPreference = 'Continue'\n          New-PACertificate ${{ env.CF_WINACME1_NAME }} -Plugin WebSelfHost -PluginArgs @{}  -Force\n        shell: pwsh\n        env:\n          CF_WINACME1_NAME: ${{ env.CF_WINACME1_NAME }}\n\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir ${{ github.workspace }}\\artifact\\upload\n          cp volume ${{ github.workspace }}\\artifact\\upload/ -Recurse -Force\n          cp acme_srv\\acme_srv.cfg ${{ github.workspace }}\\artifact\\upload\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: win-acme.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-acme.yml",
    "content": "name: CA-Handler Tests - ACME\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images against le-sim (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers-lesim:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup acme-le-sim\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n\n      - name: \"Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"ACME Profile - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"profile1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"profile2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          cd examples/Docker\n          docker logs acme-le-sim > ${{ github.workspace }}/artifact/acme-le-sim.log\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log acme-le-sim.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-container-lesim-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images for sectigo challenge\n  # ---------------------------------------------------------\n  test-containers-sectigo:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup le-sim\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          SECTIGO_SIM: true\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          cd examples/Docker\n          docker logs acme-le-sim > ${{ github.workspace }}/artifact/acme-le-sim.log\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log acme-le-sim.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-sectigo-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images against acme-le-sim with profiling\n  # ---------------------------------------------------------\n  test-containers-profiling:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup acme-le-sim-1\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          LESIM_NAME: acme-le-sim-1\n\n      - name: \"Setup acme-le-sim-2\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          LESIM_NAME: acme-le-sim-2\n\n      - name: \"Reconfigure acme-le-sim-2\"\n        run: |\n          docker stop acme-le-sim-2\n          sudo mkdir acme-le-sim-2/xca\n          sudo chmod -R 777 acme-le-sim-2/xca\n          sudo cp test/ca/acme2certifier-clean.xdb acme-le-sim-2/xca/$XCA_DB_NAME\n          sudo chmod 777 acme-le-sim-2/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > acme-le-sim-2/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"issuing_ca_name: root-ca\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"issuing_ca_key: root-ca\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> acme-le-sim-2/acme_srv.cfg\n          # sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> acme-le-sim-2/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" acme-le-sim-2/acme_srv.cfg\n          docker run -d --rm -id --network acme --name=acme-le-sim-2 -v \"$(pwd)/acme-le-sim-2\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:apache2-wsgi\n        env:\n          XCA_PASSPHRASE: ${{ env.XCA_PASSPHRASE }}\n          XCA_ISSUING_CA: ${{ env.XCA_ISSUING_CA }}\n          XCA_TEMPLATE: ${{ env.XCA_TEMPLATE }}\n          XCA_DB_NAME: ${{ env.XCA_DB_NAME }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-le-sim2/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-le-sim-2/directory\n\n      - name: \"Enroll from acme-le-sim-2\"\n        run: |\n          sudo rm -rf acme-sh/*\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue  --server http://acme-le-sim-2 --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile acme-sh/acme-sh.acme_ecc/ca.cer acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n          openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -issuer --noout | grep -i root-ca\n          sudo rm -rf acme-sh/*\n\n      - name: \"EAB Profiling - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keypath: volume/acme/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim-1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"acme_url\\\"\\: \\[\\\"http:\\/\\/acme-le-sim-2.acme\\\", \\\"http:\\/\\/acme-le-sim-1.acme\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"acme_url\\\"\\: \\\"http:\\/\\/acme-le-sim-2.acme\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"acme_keyfile\\\": \\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-2.json\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"acme_keyfile\\\": \\[\\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-1.json\\\", \\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-2.json\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n      - name: \"EAB Profiling - Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enrollment_profiling\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB ACME Profiling - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key_prof.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keypath: volume/acme/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim-1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"profile_1\\\": \\\"http:\\/\\/foo.bar\\/profile_1\\\", \\\"profile_2\\\": \\\"http:\\/\\/foo.bar\\/profile_2\\\", \\\"profile_3\\\": \\\"http:\\/\\/foo.bar\\/profile_3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile: profile_1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/profile_id/profile/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/data/acme-sh/\n          sudo cp -rp acme-le-sim-1/ ${{ github.workspace }}/artifact/data/acme-le-sim-1/\n          sudo cp -rp acme-le-sim-2/ ${{ github.workspace }}/artifact/data/acme-le-sim-2/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          docker logs acme-le-sim-1 > ${{ github.workspace }}/artifact/acme-le-sim-1.log\n          docker logs acme-le-sim-2 > ${{ github.workspace }}/artifact/acme-le-sim-2.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log acme-le-sim-1.log acme-le-sim-2.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-profiling-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images against smallstep challenge\n  # ---------------------------------------------------------\n  test-containers-smallstep:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Instanciate smallstep\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/smallstep_prep\n\n      - name: \"Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: https://step-ca.acme:9000/acme/acme\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"account_path: /\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ssl_verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Enroll via acme_ca_handler 1st attempt\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-srv.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Enroll via acme_ca_handler 2nd attempt\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-srv.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          cd examples/Docker\n          docker logs step-ca > ${{ github.workspace }}/artifact/step-ca.log\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log step-ca.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-smallstep-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images against Letsencrypt staging\n  # including profile sync and renewal info checking\n  # ---------------------------------------------------------\n  test-containers-letsencrypt:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret - ACMEDNS_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ACMEDNS_CFG }}\n\n      - name: \"Parse JSON secret - CF_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CF_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"CloudFlare - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/acme.sh --output examples/Docker/data/acme/acme.sh\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/dnsapi/dns_cf.sh --output examples/Docker/data/acme/dns_cf.sh\n          sudo chmod a+x examples/Docker/data/acme/*.sh\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: https://acme-staging-v02.api.letsencrypt.org\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindelsack@gmail.com\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_sh_script: volume/acme/acme.sh\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_update_script: volume/acme/dns_cf.sh\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_update_script_variables: {\\\"CF_Token\\\": \\\"$CF_TOKEN\\\", \\\"CF_Zone_ID\\\": \\\"$CF_ZONE_ID\\\"}\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_sh_shell: /bin/bash\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_validation_timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profiles_sync: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"renewalinfo_lookup: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme.dynamop.de\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          # sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 15/g\" examples/Docker/data/acme_srv.cfg\n        env:\n          CF_TOKEN: ${{ env.CF_TOKEN }}\n          CF_ZONE_ID: ${{ env.CF_ZONE_ID }}\n\n      - name: \"CloudFlare - Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Compare profile information\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/compare_profile_info\n        with:\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Compare renewal information between A2C and LE\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/compare_renewal_info\n        with:\n          NAME_SPACE: acme.dynamop.de\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          CERTIFICATE_FILE: \"lego/certificates/lego-${{ env.UUID }}.acme.dynamop.de.crt\"\n\n      - name: \"Generate new UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_dns\n        with:\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          NAME_SPACE: acme.dynamop.de\n          CERT_TIMEOUT: \"360\"\n          VERIFY_CERT: false\n\n      - name: \"ACMEDNS - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/acme.sh --output examples/Docker/data/acme/acme.sh\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/dnsapi/dns_acmedns.sh --output examples/Docker/data/acme/dns_acmedns.sh\n          sudo chmod a+x examples/Docker/data/acme/*.sh\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/le_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: https://acme-staging-v02.api.letsencrypt.org\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: grindelsack@gmail.com\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_sh_script: volume/acme/acme.sh\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_update_script: volume/acme/dns_acmedns.sh\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_update_script_variables: {\\\"ACMEDNS_USERNAME\\\": \\\"$ACMEDNS_USERNAME\\\", \\\"ACMEDNS_PASSWORD\\\": \\\"$ACMEDNS_PASSWORD\\\", \\\"ACMEDNS_BASE_URL\\\": \\\"https://auth.acme-dns.io\\\", \\\"ACMEDNS_SUBDOMAIN\\\": \\\"$ACMEDNS_SUBDOMAIN\\\"}\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_sh_shell: /bin/bash\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dns_validation_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme.dynamop.de\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profiles_sync: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"renewalinfo_lookup: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 15/g\" examples/Docker/data/acme_srv.cfg\n\n          cd examples/Docker/\n          docker compose restart\n        env:\n          ACMEDNS_USERNAME: ${{ env.ACMEDNS_USERNAME }}\n          ACMEDNS_PASSWORD: ${{ env.ACMEDNS_PASSWORD }}\n          ACMEDNS_SUBDOMAIN: ${{ env.ACMEDNS_SUBDOMAIN }}\n\n      - name: \"ACMEDNS - WC - Enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_dns_wc\n        with:\n          NAME_SPACE: acme.dynamop.de\n          CERT_TIMEOUT: 360\n          VERIFY_CERT: false\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-letsencrypt-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPM against le-sim\n  # ---------------------------------------------------------\n  test-rpm-lesim:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup le-sim\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n\n      - name: \"Prepare setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          # docker exec acme-srv grep -i \"found in keyfile\" /var/log/messages\n          docker exec acme-srv bash -c \"grep -i \\\"found in keyfile\\\" /var/log/messages\"\n\n      - name: \"ACME Profile - Setup acme ca_handler\"\n        run: |\n          sudo mkdir -p data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"profile1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"profile2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n\n      - name: \"ACME Profile - Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          docker logs acme-le-sim > ${{ github.workspace }}/artifact/le-sim.log\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-lesim-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPM or sectigo challenge\n  # ---------------------------------------------------------\n  test-rpm-sectigo:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup le-sim\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          SECTIGO_SIM: true\n\n      - name: \"Prepare setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          docker exec acme-srv bash -c \"grep -i \\\"found in keyfile\\\" /var/log/messages\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          docker logs acme-le-sim > ${{ github.workspace }}/artifact/le-sim.log\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-sectigo-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPM for profiling\n  # ---------------------------------------------------------\n  test-rpm-profiling:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup acme-le-sim-1\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          LESIM_NAME: acme-le-sim-1\n\n      - name: \"Setup acme-le-sim-2\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/le-sim_prep\n        with:\n          LESIM_NAME: acme-le-sim-2\n\n      - name: \"Reconfigure acme-le-sim-2\"\n        run: |\n          docker stop acme-le-sim-2\n          sudo mkdir acme-le-sim-2/xca\n          sudo chmod -R 777 acme-le-sim-2/xca\n          sudo cp test/ca/acme2certifier-clean.xdb acme-le-sim-2/xca/$XCA_DB_NAME\n          sudo chmod 777 acme-le-sim-2/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > acme-le-sim-2/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"issuing_ca_name: root-ca\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"issuing_ca_key: root-ca\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> acme-le-sim-2/acme_srv.cfg\n          # sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> acme-le-sim-2/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> acme-le-sim-2/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" acme-le-sim-2/acme_srv.cfg\n          docker run -d --rm -id --network acme --name=acme-le-sim-2 -v \"$(pwd)/acme-le-sim-2\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:apache2-wsgi\n        env:\n          XCA_PASSPHRASE: ${{ env.XCA_PASSPHRASE }}\n          XCA_ISSUING_CA: ${{ env.XCA_ISSUING_CA }}\n          XCA_TEMPLATE: ${{ env.XCA_TEMPLATE }}\n          XCA_DB_NAME: ${{ env.XCA_DB_NAME }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-le-sim2/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-le-sim-2/directory\n\n      - name: \"Enroll from acme-le-sim-2\"\n        run: |\n          sudo rm -rf acme-sh/*\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue  --server http://acme-le-sim-2 --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile acme-sh/acme-sh.acme_ecc/ca.cer acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n          openssl x509 -in acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer -issuer --noout | grep -i root-ca\n\n      - name: \"EAB Profiling - Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/acme_ca/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keypath: /opt/acme2certifier/volume/acme_ca/\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim-1\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"acme_url\\\"\\: \\[\\\"http:\\/\\/acme-le-sim-2.acme\\\", \\\"http:\\/\\/acme-le-sim-1.acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"acme_url\\\"\\: \\\"http:\\/\\/acme-le-sim-2.acme\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"acme_keyfile\\\": \\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-2.json\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"acme_keyfile\\\": \\[\\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-1.json\\\", \\\"\\/var\\/www\\/acme2certifier\\/volume\\/acme-le-sim-2.json\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB Profiling - Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"EAB Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enrollment_profiling\n\n      - name: \"EAB ACME Profiling - Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/acme_ca\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/acme_ca/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keypath: /opt/acme2certifier/volume/acme_ca/\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: http://acme-le-sim-1\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"profile_1\\\": \\\"http:\\/\\/foo.bar\\/profile_1\\\", \\\"profile_2\\\": \\\"http:\\/\\/foo.bar\\/profile_2\\\", \\\"profile_3\\\": \\\"http:\\/\\/foo.bar\\/profile_3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo \"profile: profile_1\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/profile_id/profile/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"EAB ACME Profiling - Run Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp acme-le-sim-1/ ${{ github.workspace }}/artifact/data/acme-le-sim-1/\n          sudo cp -rp acme-le-sim-2/ ${{ github.workspace }}/artifact/data/acme-le-sim-2/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh acme-le-sim-1.log acme-le-sim-2.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-profiling-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPM against smallstep\n  # ---------------------------------------------------------\n  test-rpm-smallstep:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Instanciate smallstep\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/smallstep_prep\n\n      - name: \"Prepare setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: https://step-ca.acme:9000/acme/acme\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindsa@foo.bar\" >> data/volume/acme_srv.cfg\n          sudo echo \"account_path: /\" >> data/volume/acme_srv.cfg\n          sudo echo \"ssl_verify: False\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Enroll via acme_ca_handler 1st attempt\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-srv.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Enroll via acme_ca_handler 2nd attempt\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-srv.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          # docker exec acme-srv grep -i \"found in keyfile\" /var/log/messages\n          docker exec acme-srv bash -c \"grep -i \\\"found in keyfile\\\" /var/log/messages\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-smallstep-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPM against Letsencrypt staging\n  # including profile sync and renewal info checking\n  # ---------------------------------------------------------\n  test-rpm-letsencrypt:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret - GH_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse JSON secret - ACMEDNS_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ACMEDNS_CFG }}\n\n      - name: \"Parse JSON secret - CF_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CF_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/acme.sh --output data/volume/acme_ca/acme.sh\n          sudo curl https://raw.githubusercontent.com/acmesh-official/acme.sh/refs/heads/master/dnsapi/dns_cf.sh --output data/volume/acme_ca/dns_cf.sh\n          sudo chmod a+x data/volume/acme_ca/*.sh\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /opt/acme2certifier/volume/le_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: https://acme-staging-v02.api.letsencrypt.org\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: grindelsack@gmail.com\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_sh_script: /opt/acme2certifier/volume/acme_ca/acme.sh\" >> data/volume/acme_srv.cfg\n          sudo echo \"dns_update_script: /opt/acme2certifier/volume/acme_ca/dns_cf.sh\" >> data/volume/acme_srv.cfg\n          sudo echo \"dns_update_script_variables: {\\\"CF_Token\\\": \\\"$CF_TOKEN\\\", \\\"CF_Zone_ID\\\": \\\"$CF_ZONE_ID\\\"}\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_sh_shell: /bin/bash\" >> data/volume/acme_srv.cfg\n          sudo echo \"dns_validation_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"profiles_sync: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"renewalinfo_lookup: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme.dynamop.de\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n        env:\n          CF_TOKEN: ${{ env.CF_TOKEN }}\n          CF_ZONE_ID: ${{ env.CF_ZONE_ID }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Compare profile information\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/compare_profile_info\n        with:\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Compare renewal information between A2C and LE\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/compare_renewal_info\n        with:\n          NAME_SPACE: acme.dynamop.de\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          CERTIFICATE_FILE: \"lego/certificates/lego-${{ env.UUID }}.acme.dynamop.de.crt\"\n\n      - name: \"Generate new UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Enrollment\"\n        uses: ./.github/actions/wf_specific/acme_ca_handler/enroll_dns\n        with:\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          NAME_SPACE: acme.dynamop.de\n          CERT_TIMEOUT: \"360\"\n          VERIFY_CERT: false\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          # docker exec acme-srv grep -i \"found in keyfile\" /var/log/messages\n          docker exec acme-srv bash -c \"grep -i \\\"found in keyfile\\\" /var/log/messages\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-letsencrypt-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-asa.yml",
    "content": "name: CA-Handler Tests - Insta ASA CA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # test headerinfo feature for asa handler the first request\n  # might fail for unknown reasons\n  # ---------------------------------------------------------\n  test-containers-headerinfo:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2']\n        dbhandler: ['wsgi']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse ASA configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ASA_CFG }}\n          uppercase: 'true'\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"a2c configuration with standard profile\"\n        run: |\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible again\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Create lego folder\"\n        run: |\n          mkdir lego\n\n      - name: \"Enroll lego with profileID ACME - could potenially fail\"\n        continue-on-error: True\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_name=ACME -d lego.acme --key-type rsa2048 --http run\n\n      - name: \"Enroll acme.sh with profileID ACME\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --useragent profile_name=ACME --keylength 2048 --debug 3 --output-insecure\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n          openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Digital Signature\"\n\n      - name: \"Enroll lego with profileID ACME\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_name=ACME -d lego.acme --key-type rsa2048 --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n          sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Digital Signature\"\n\n      - name: \"Enroll acme.sh with profileID ACME_2\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --renew --server http://acme-srv --force -d acme-sh.acme --standalone --useragent profile_name=ACME_2 --keylength 2048 --debug 3 --output-insecure\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme/acme-sh.acme.cer\n          openssl x509 -in acme-sh/acme-sh.acme/acme-sh.acme.cer -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n\n      - name: \"Enroll lego with profileID ACME_2\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent profile_name=ACME_2 -d lego.acme --key-type rsa2048 --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n          sudo openssl x509 -in lego/certificates/lego.acme.crt -ext keyUsage -noout | grep \"Key Encipherment, Data Encipherment\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-headerinfo.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: [guard, test-containers-headerinfo]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 2\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse ASA configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ASA_CFG }}\n          uppercase: 'true'\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Profile $ASA_PROFILE1 - Setup a2c with asa_ca_handler with profile $ASA_PROFILE1\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"create folders\"\n        run: |\n          mkdir lego\n          mkdir acme-sh\n          mkdir certbot\n\n      - name: \"$ASA_PROFILE1 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_1\n\n      - name: \"Profile $ASA_PROFILE2 - Reconfiguration of a2c with a new profile\"\n        run: |\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE2\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"$ASA_PROFILE2 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_2\n        with:\n          PROFILE: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Header-info - Setup asa_ca_handler with headerinfo\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Hederinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_headerinfo\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB without headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_wo_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"EAB with headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_w_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"ACME Profile - Setup asa_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_acmeprofile\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB ACME Profiling - Setup asa_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_acmeprofile\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: [guard, test-containers-headerinfo]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 2\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse ASA configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ASA_CFG }}\n          uppercase: 'true'\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Profile $ASA_PROFILE1 - Setup a2c with asa_ca_handler with profile $ASA_PROFILE1\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Create letsencrypt and lego folder\"\n        run: |\n          mkdir certbot\n          mkdir lego\n          mkdir acme-sh\n\n      - name: \"$ASA_PROFILE1 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_1\n        with:\n          PROFILE: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Profile $ASA_PROFILE2 - Setup a2c with asa_ca_handler with profile $ASA_PROFILE1\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE2\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Profile $ASA_PROFILE2 - reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"$ASA_PROFILE2 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_2\n        with:\n          PROFILE: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Header-info - Setup asa_ca_handler with headerinfo\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Profile $ASA_PROFILE2 - reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Hederinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_headerinfo\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB without headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"EAB without headerinfo - Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_wo_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"EAB with headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"EAB with headerinfo - Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_w_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"ACME Profile - Setup asa_ca_handler with headerinfo\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"ACME Profile - reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile  - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_acmeprofile\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB ACME Profiling - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"EAB ACME Profiling - Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_acmeprofile\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: [guard, test-containers-headerinfo]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 2\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse ASA configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.ASA_CFG }}\n          uppercase: 'true'\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Profile $ASA_PROFILE1 - Setup a2c with asa_ca_handler with profile $ASA_PROFILE1\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Create letsencrypt and lego folder\"\n        run: |\n          mkdir certbot\n          mkdir lego\n          mkdir acme-sh\n\n      - name: \"$ASA_PROFILE1 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_1\n        with:\n          PROFILE: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Profile $ASA_PROFILE2 - Setup a2c with asa_ca_handler with profile $ASA_PROFILE1\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE2\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"$ASA_PROFILE2 - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_profile_2\n        with:\n          PROFILE: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"Header-info - Setup asa_ca_handler with headerinfo\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Hederinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_headerinfo\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB without headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_wo_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"EAB with headerinfo - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_w_headerinfo\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n\n      - name: \"ACME Profile - Setup asa_ca_handler with headerinfo\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile  - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_acmeprofile\n        with:\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ env.ASA_PROFILE2 }}\n\n      - name: \"EAB ACME Profiling - Setup asa_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/asa_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $ASA_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $ASA_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $ASA_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $ASA_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $ASA_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $ASA_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_name: $ASA_PROFILE1\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"$ASA_PROFILE1\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"$ASA_PROFILE2\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_name\\\"\\: \\[\\\"$ASA_PROFILE2\\\", \\\"$ASA_PROFILE1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_name\\\"\\: \\\"$ASA_PROFILE3\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"$ASA_CA_NAME2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/asa_ca_handler/enroll_eab_acmeprofile\n        with:\n          ASA_CA_NAME1: ${{ env.ASA_CA_NAME1 }}\n          ASA_CA_NAME2: ${{ env.ASA_CA_NAME2 }}\n          ASA_PROFILE1: ${{ env.ASA_PROFILE1 }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-certifier.yml",
    "content": "name: CA-Handler Tests - Insta Certifier\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 2\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse NCM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCM_CFG }}\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NCM_API_HOST: ${{ env.NCM_API_HOST }}\n          NCM_API_USER: ${{ env.NCM_API_USER }}\n          NCM_API_PASSWORD: ${{ env.NCM_API_PASSWORD }}\n\n      - name: \"No profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"No profile - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_no_profile\n\n      - name: \"Profile 101 - Setup a2c with certifier_ca_handler with profile 101\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_id: 101\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Profile 101 - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_101_profile\n\n      - name: \"Profile 102 - Setup a2c with certifier_ca_handler with Profile 102\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_id: 102\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Profile 102 - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_102_profile\n\n      - name: \"Header-info - Setup a2c with certifier_ca_handler with header-info\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Header-info - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_headerinfo\n\n      - name: \"EAB without headerinfo - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB without headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_wo_headerinfo\n\n      - name: \"EAB with headerinfo - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB with headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n\n      - name: \"EAB with headerinfo - Reconfigure key_file without restarting\"\n        run: |\n          sudo sed -i \"s/\\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\"\\]/\\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\", \\\"*.acme\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/      \\\"prevalidated_domainlist\\\": \\[\\\"www.example.com\\\"\\]/          \\\"prevalidated_domainlist\\\": \\[\\\"www.example1.com\\\"\\]\\n  },\\n  \\\"keyid_04\\\": {\\n    \\\"hmac\\\": \\\"YW5kX2hlcmVfaXNfYW5vdGhlcl92ZXJ5X2xvbmdfbWFja19obWFjX2tleV90b19jaGVja19pZl9jaGFuZ2VzX2FmZmVjdF9pbW1lZGF0ZWx5\\\",\\n    \\\"cahandler\\\": {}\\n  }\\n}/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '34,35d' examples/Docker/data/kid_profiles.json\n\n      - name: \"EAB with headerinfo - Enrollment after reconfiguration\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n        with:\n          RECONFIGURE: true\n\n      - name: \"kid-file in yaml format - Reconfiguration\"\n        run: |\n          sudo sed -i \"s/kid_profiles.json/kid_profiles.yml/g\" examples/Docker/data/acme_srv.cfg\n          sudo pip3 install yq\n          sudo pip3 install jq\n          sudo sh -c \"cat examples/Docker/data/kid_profiles.json | yq -y '.' > examples/Docker/data/kid_profiles.yml\"\n          sudo rm examples/Docker/data/kid_profiles.json\n          sudo sed -i '33,34d' examples/Docker/data/kid_profiles.yml\n          # sudo cat examples/Docker/data/kid_profiles.yml\n\n      - name: \"kid-file in yaml format - Enrollment after reconfiguration\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n        with:\n          RECONFIGURE: true\n\n      - name: \"ACME Profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"101\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"102\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"ACME Profile - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"101\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"102\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profile - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: container\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 2\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: Parse GitHub secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse NCM_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCM_CFG }}\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NCM_API_HOST: ${{ env.NCM_API_HOST }}\n          NCM_API_USER: ${{ env.NCM_API_USER }}\n          NCM_API_PASSWORD: ${{ env.NCM_API_PASSWORD }}\n\n      - name: \"No profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"No profile - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_no_profile\n\n      - name: \"Profile 101 - Setup a2c with certifier_ca_handler with profile 101\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_id: 101\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Profile 101 - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_101_profile\n\n      - name: \"Profile 102 - Setup a2c with certifier_ca_handler with profile 101\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_id: 102\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Profile 102 - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_102_profile\n\n      - name: \"Header-info - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Header-info - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_headerinfo\n\n      - name: \"EAB without headerinfo - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB without headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_wo_headerinfo\n\n      - name: \"EAB with headerinfo - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n\n      - name: \"EAB with headerinfo - Reconfigure key_file without restarting\"\n        run: |\n          sudo sed -i \"s/\\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\"\\]/\\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\", \\\"*.acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/      \\\"prevalidated_domainlist\\\": \\[\\\"www.example.com\\\"\\]/          \\\"prevalidated_domainlist\\\": \\[\\\"www.example1.com\\\"\\]\\n  },\\n  \\\"keyid_04\\\": {\\n    \\\"hmac\\\": \\\"YW5kX2hlcmVfaXNfYW5vdGhlcl92ZXJ5X2xvbmdfbWFja19obWFjX2tleV90b19jaGVja19pZl9jaGFuZ2VzX2FmZmVjdF9pbW1lZGF0ZWx5\\\",\\n    \\\"cahandler\\\": {}\\n  }\\n}/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '34,35d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Update configuration\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT update\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - Enrollment after reconfiguration\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n        with:\n          RECONFIGURE: true\n\n      - name: \"kid-file in yaml format - Reconfiguration\"\n        run: |\n          sudo sed -i \"s/kid_profiles.json/kid_profiles.yml/g\" data/volume/acme_srv.cfg\n          sudo pip3 install yq\n          sudo pip3 install jq\n          sudo sh -c \"cat data/volume/acme_ca/kid_profiles.json | yq -y '.' > data/volume/acme_ca/kid_profiles.yml\"\n          sudo rm data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '33,34d' data/volume/acme_ca/kid_profiles.yml\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"kid-file in yaml format - Enrollment after reconfiguration\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_headerinfo\n        with:\n          RECONFIGURE: true\n\n      - name: \"ACME Profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"101\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"102\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - Enrollmnet\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profile - Setup a2c with certifier_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:8084\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          # sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo echo \"profile_id: 100\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"101\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"102\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_operations_log: True/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"profile_id\\\"\\: \\[\\\"102\\\", \\\"101\\\"\\, \\\"100\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"profile_id\\\"\\: \\\"102\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"SubCA2\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - Enrollment\"\n        uses: ./.github/actions/wf_specific/certifier_ca_handler/enroll_eab_w_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: rpm\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-cmp.yml",
    "content": "name: CA-Handler Tests - CMPV2\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse CMP configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CMP_CFG }}\n          uppercase: 'true'\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Create ssh environment on ramdisk\"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ env.SSH_KEY }}\n          KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n\n      - name: \"Setup ssh forwarder\"\n        run: |\n            docker run -d --rm --network acme --name=ssh-forwarder.acme  -e \"MAPPINGS=8086:$CMP_HOST:8086\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 443:443 -p 445:445 -p 88:88 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n        env:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST}}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          CMP_HOST: ${{ env.CMP_HOST }}\n\n      - name: \"Setup a2c with cmp_ca_handler with key-cert authentication\"\n        run: |\n          sudo touch examples/Docker/data/ca_bundle.pem\n          sudo touch examples/Docker/data/ra_cert.pem\n          sudo touch examples/Docker/data/ra_key.pem\n          sudo chmod 777 examples/Docker/data/*.pem\n          sudo echo \"$CMP_TRUSTED\" > examples/Docker/data/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > examples/Docker/data/ra_cert.pem\n          sudo echo \"$CMP_RA_KEY\" > examples/Docker/data/ra_key.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"cmp_server: $RUNNER_IP:8086\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_cert: volume/ra_cert.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_key: volume/ra_key.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_trusted: volume/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_KEY: ${{ env.CMP_RA_KEY }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n          sudo rm -rf acme-sh/*\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail01\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail01.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n          exit 1\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Setup a2c with cmp_ca_handler with PSK refnum authentication\"\n        run: |\n          sudo touch examples/Docker/data/ca_bundle.pem\n          sudo touch examples/Docker/data/ra_cert.pem\n          sudo chmod 777 examples/Docker/data/*.pem\n          sudo echo \"$CMP_TRUSTED\" > examples/Docker/data/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > examples/Docker/data/ra_cert.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"cmp_server: $RUNNER_IP:8086\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_cert: volume/ra_cert.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_trusted: volume/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_ref: $CMP_REF\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cmp_secret: $CMP_SECRET\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n          CMP_REF: ${{ env.CMP_REF }}\n          CMP_SECRET: ${{ env.CMP_SECRET }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail02\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail02.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n          exit 1\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        # not possible on RH8 due to OpenSSL 3 dependency\n        rhversion: [9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse CMP configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CMP_CFG }}\n          uppercase: 'true'\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create ssh environment on ramdisk\"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ env.SSH_KEY }}\n          KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n\n      - name: \"Setup ssh forwarder\"\n        run: |\n            docker run -d --rm --network acme --name=ssh-forwarder.acme  -e \"MAPPINGS=8086:$CMP_HOST:8086\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 443:443 -p 445:445 -p 88:88 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n        env:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST}}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          CMP_HOST: ${{ env.CMP_HOST }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Setup a2c with cmp_ca_handler\"\n        run: |\n          sudo mkdir data/volume/acme_ca\n          sudo cp test/ca/root-ca-cert.pem data/volume/acme_ca\n          sudo cp test/ca/sub-ca-cert.pem data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_bundle.pem\n          sudo touch data/volume/acme_ca/ra_cert.pem\n          sudo touch data/volume/acme_ca/ra_key.pem\n          sudo chmod 777 data/volume/acme_ca/*.pem\n          sudo echo \"$CMP_TRUSTED\" > data/volume/acme_ca/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > data/volume/acme_ca/ra_cert.pem\n          sudo echo \"$CMP_RA_KEY\" >  data/volume/acme_ca/ra_key.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_cert: /opt/acme2certifier/volume/acme_ca/ra_cert.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_key: /opt/acme2certifier/volume/acme_ca/ra_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_trusted: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_KEY: ${{ env.CMP_RA_KEY }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail01\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail01.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n          exit 1\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"Setup a2c with cmp_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca\n          sudo cp test/ca/root-ca-cert.pem data/volume/acme_ca\n          sudo cp test/ca/sub-ca-cert.pem data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_bundle.pem\n          sudo touch data/volume/acme_ca/ra_cert.pem\n          sudo chmod 777 data/volume/acme_ca/*.pem\n          sudo echo \"$CMP_TRUSTED\" > data/volume/acme_ca/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > data/volume/acme_ca/ra_cert.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_cert: /opt/acme2certifier/volume/acme_ca/ra_cert.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_trusted: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ref: $CMP_REF\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_secret: $CMP_SECRET\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n          CMP_REF: ${{ env.CMP_REF }}\n          CMP_SECRET: ${{ env.CMP_SECRET }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail02\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail02.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n          exit 1\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse CMP configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.CMP_CFG }}\n          uppercase: 'true'\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create ssh environment on ramdisk\"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ env.SSH_KEY }}\n          KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n\n      - name: \"Setup ssh forwarder\"\n        run: |\n            docker run -d --rm --network acme --name=ssh-forwarder.acme  -e \"MAPPINGS=8086:$CMP_HOST:8086\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 443:443 -p 445:445 -p 88:88 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n        env:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST}}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          CMP_HOST: ${{ env.CMP_HOST }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Setup a2c with cmp_ca_handler\"\n        run: |\n          sudo mkdir data/volume/acme_ca\n          sudo cp test/ca/root-ca-cert.pem data/volume/acme_ca\n          sudo cp test/ca/sub-ca-cert.pem data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_bundle.pem\n          sudo touch data/volume/acme_ca/ra_cert.pem\n          sudo touch data/volume/acme_ca/ra_key.pem\n          sudo chmod 777 data/volume/acme_ca/*.pem\n          sudo echo \"$CMP_TRUSTED\" > data/volume/acme_ca/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > data/volume/acme_ca/ra_cert.pem\n          sudo echo \"$CMP_RA_KEY\" >  data/volume/acme_ca/ra_key.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_cert: /var/www/acme2certifier/volume/acme_ca/ra_cert.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_key: /var/www/acme2certifier/volume/acme_ca/ra_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_trusted: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_KEY: ${{ env.CMP_RA_KEY }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail01\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail01.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail01.outcome }}\"\n          exit 1\n\n      - name: \"Setup a2c with cmp_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca\n          sudo cp test/ca/root-ca-cert.pem data/volume/acme_ca\n          sudo cp test/ca/sub-ca-cert.pem data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_bundle.pem\n          sudo touch data/volume/acme_ca/ra_cert.pem\n          sudo chmod 777 data/volume/acme_ca/*.pem\n          sudo echo \"$CMP_TRUSTED\" > data/volume/acme_ca/ca_bundle.pem\n          sudo echo \"$CMP_RA_CERT\" > data/volume/acme_ca/ra_cert.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/cmp_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_path: pkix/\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ignore_keyusage: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_msg_timeout: 3\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_total_timeout: 5\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_server: ssh-forwarder.acme:8086\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_cert: /var/www/acme2certifier/volume/acme_ca/ra_cert.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_trusted: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_recipient: $CMP_RECIPIENT\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_ref: $CMP_REF\" >> data/volume/acme_srv.cfg\n          sudo echo \"cmp_secret: $CMP_SECRET\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          CMP_RECIPIENT: ${{ env.CMP_RECIPIENT }}\n          CMP_RA_CERT: ${{ env.CMP_RA_CERT }}\n          CMP_TRUSTED: ${{ env.CMP_TRUSTED }}\n          CMP_REF: ${{ env.CMP_REF }}\n          CMP_SECRET: ${{ env.CMP_SECRET }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv --accountemail 'acme-sh@example.com' -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Allowed domainlist feature - Enroll lego (fail)\"\n        continue-on-error: true\n        id: legofail02\n        run: |\n          docker run -i --rm -v $PWD/lego:/.lego/ --name lego --network acme goacme/lego -s  https://acme-srv --tls-skip-verify -a --email \"lego@example.com\" -d lego --tls run\n\n      - name: \"Allowed domainlist feature - check  result \"\n        if: ${{ steps.legofail02.outcome != 'failure' }}\n        run: |\n          echo \"legofail outcome is ${{steps.legofail02.outcome }}\"\n          exit 1\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-digicert.yml",
    "content": "name: CA-Handler Tests - Digicert CertCentral\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' && (inputs.branch == 'master' || inputs.branch == 'devel') }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        websrv: ['apache2']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse DIGICERT_CFG JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.DIGICERT_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          NAME_SPACE: acme.dynamop.de\n          USE_CERTBOT: false\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB - Setup a2c with digicert_ca_handler\"\n        run: |\n          mkdir -p examples/Docker/data\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"EAB - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab\n\n      - name: \"ACME Profile - Setup a2c with digicert_ca_handler\"\n        run: |\n          mkdir -p examples/Docker/data\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n\n      - name: \"EAB ACME Profile - Setup a2c with digicert_ca_handler\"\n        run: |\n          mkdir -p examples/Docker/data\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: [guard, test-containers]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        rhversion: [8]\n        execscript: ['rpm_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse DIGICERT_CFG JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.DIGICERT_CFG }}\n\n      - name: \"Parse GH_CFG JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Setup a2c with digicert_ca_handler for django\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          NAME_SPACE: acme.dynamop.de\n          USE_CERTBOT: false\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"EAB - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab\n\n      - name: \"ACME Profile -  - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"EAB ACME Profile - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: [guard, test-rpm]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['nginx']\n        execscript: ['django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse DIGICERT_CFG JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.DIGICERT_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n          NAME_SPACE: acme.dynamop.de\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup a2c with digicert_ca_handler for django\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          NAME_SPACE: acme.dynamop.de\n          USE_CERTBOT: false\n          TEST_ADL: \"true\"\n\n      - name: \"EAB - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab\n\n      - name: \"ACME Profile -  - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"deb\"\n\n      - name: \"EAB ACME Profile - Setup a2c with digicert_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem data/ca_certs.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/digicert_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_key: $DIGICERT_API_KEY\" >> data/volume/acme_srv.cfg\n          sudo echo \"organization_name: $DIGICERT_ORGNAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DIGICERT_DOMAIN\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"ssl_basic\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"ssl_securesite_pro\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"ssl_securesite_flex\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_type\\\"\\: \\[\\\"ssl_basic\\\", \\\"ssl_securesite_pro\\\", \\\"ssl_securesite_flex\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_type\\\"\\: \\\"ssl_securesite_pro\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme.dynamop.de/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          DIGICERT_API_KEY: ${{ env.DIGICERT_API_KEY }}\n          DIGICERT_ORGNAME: ${{ env.DIGICERT_ORGNAME }}\n          DIGICERT_DOMAIN: ${{ env.DIGICERT_DOMAIN }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/digicert_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"deb\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier/\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-dogtag.yml",
    "content": "name: CA handler tests - DogTag\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' && (inputs.branch == 'master' || inputs.branch == 'devel') }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  acme-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: server.acme\n\n\n      - name: \"Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/harica_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: $ACME_URL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"account_path: /acct/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: $ACME_ACCOUNT_EMAIL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_kid: $EAB_KID\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_hmac_key: $EAB_HMAC_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DOMAIN\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n        env:\n          ACME_ACCOUNT_EMAIL: ${{ secrets.EMAIL }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/harica/acme_enroll\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: acme-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-ejbca.yml",
    "content": "name: CA-Handler Tests - EJBCA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Instanciate ejbca\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/ejbca_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}/examples/Docker\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Default - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/ejbca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_file: volume/acme_ca/superadmin.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"EAB without headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/ejbca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_file: volume/acme_ca/superadmin.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n          docker compose logs\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_wo_headerinfo\n\n      - name: \"EAB with headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/ejbca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_file: volume/acme_ca/superadmin.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n          docker compose logs\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_w_headerinfo\n\n      - name: \"ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/ejbca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_file: volume/acme_ca/superadmin.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/ejbca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_file: volume/acme_ca/superadmin.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          docker logs ejbca > ${{ github.workspace }}/artifact/ejbca.log\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/a2c.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz ejbca.log a2c.log data acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Instanciate ejbca\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/ejbca_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Default - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /opt/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"EAB without headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /opt/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_wo_headerinfo\n\n      - name: \"EAB with headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /opt/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_w_headerinfo\n\n      - name: \"ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /opt/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /opt/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker logs ejbca > ${{ github.workspace }}/artifact/ejbca.log\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data ejbca.log acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Instanciate ejbca\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/ejbca_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}\n\n      - name: \"Default - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /var/www/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"EAB without headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /var/www/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB without headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_wo_headerinfo\n\n      - name: \"EAB with headerinfo - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /var/www/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_w_headerinfo\n\n      - name: \"ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /var/www/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with ejbca_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/ejbca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://ejbca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_file: /var/www/acme2certifier/volume/acme_ca/superadmin.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: $SAEC\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: acmesubca\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: acmeca1\" >> data/volume/acme_srv.cfg\n          sudo echo \"ee_profile_name: acmeca\" >> data/volume/acme_srv.cfg\n          sudo echo \"username: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_code: acme_srv\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acmeca1\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"acmeca2\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"acmeca2\\\", \\\"acmeca1\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"acmeca2\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"ca_name\\\": \\\"acmeca\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          SAEC: ${{ env.SAEC }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/ejbca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-est.yml",
    "content": "name: CA-Handler Tests - EST\n# Clientauth tests are not working on testrfc7030 and are done insed openxpi wf\non:\n  push:\n    branches: [ 'disabled']\n\n#  workflow_dispatch:\n#    inputs:\n#      branch:\n#        description: 'Branch to run the workflow on'\n#        required: true\n#        default: 'main'\n#      run_id:\n#        description: 'Run ID of the producing workflow'\n#        required: true\n#        default: '0'\n#      sha:\n#        description: 'SHA of the commit to run the workflow on'\n#        required: true\n#        default: ''\n#      called_by_workflow:\n#        description: 'Name of the producing workflow'\n#        required: true\n#        default: 'manual-trigger'\n#      full_ref:\n#        description: 'Full git ref of the commit to run the workflow on'\n#        required: false\n#        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup esthandler using http-basic-auth\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/est_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_host: https://testrfc7030.com:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_user: estuser\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_password: estpwd\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 45\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          VERIFY_CERT: \"false\"\n          USE_CERTBOT: \"false\"\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: [guard, test-containers]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n\n      - name: \"setup esthandler using http-basic-auth\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://testrfc7030.com:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_user: estuser\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_password: estpwd\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          VERIFY_CERT: \"false\"\n          USE_CERTBOT: \"false\"\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: [guard, test-rpm]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"setup esthandler using http-basic-auth\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://testrfc7030.com:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_user: estuser\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_password: estpwd\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          VERIFY_CERT: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-freeipa.yml",
    "content": "name: CA handler tests - FreeIPA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' && (inputs.branch == 'master' || inputs.branch == 'devel') }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  acme-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: server.acme\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/harica_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: $ACME_URL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"account_path: /acct/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: $ACME_ACCOUNT_EMAIL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_kid: $EAB_KID\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_hmac_key: $EAB_HMAC_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DOMAIN\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n        env:\n          ACME_ACCOUNT_EMAIL: ${{ secrets.EMAIL }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/harica/acme_enroll\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: acme-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-harica.yml",
    "content": "name: CA handler tests - Harica\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' && (inputs.branch == 'master' || inputs.branch == 'devel') }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  acme-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: Parse HARICA config\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.HARICA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme\n          sudo chmod -R 777 examples/Docker/data/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/harica_staging_private_key.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_url: $ACME_URL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"account_path: /acct/\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"acme_account_email: $ACME_ACCOUNT_EMAIL\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_kid: $EAB_KID\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_hmac_key: $EAB_HMAC_KEY\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DOMAIN\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n        env:\n          ACME_ACCOUNT_EMAIL: ${{ secrets.EMAIL }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/harica/acme_enroll\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"Check acme account found in keyfile\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -i \"found in keyfile\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: acme-test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: [guard, acme-test-containers]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        rhversion: [9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: Parse HARICA config\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.HARICA_CFG }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme\n          sudo chmod -R 777 data/volume/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: volume/acme/harica_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: $ACME_URL\" >> data/volume/acme_srv.cfg\n          sudo echo \"account_path: /acct/\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: $ACME_ACCOUNT_EMAIL\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_kid: $EAB_KID\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_hmac_key: $EAB_HMAC_KEY\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DOMAIN\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" data/volume/acme_srv.cfg\n        env:\n          ACME_ACCOUNT_EMAIL: ${{ secrets.EMAIL }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/harica/acme_enroll\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          docker logs acme-le-sim > ${{ github.workspace }}/artifact/le-sim.log\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: acme-test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: [guard, acme-test-containers, test-rpm]\n    runs-on: ubuntu-latest\n    # if: github.repository == 'grindsa/acme2certifier'\n    if: github.repository == 'grindsa/disabled'\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        websrv: ['nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: Parse HARICA config\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.HARICA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Setup acme_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme\n          sudo chmod -R 777 data/volume/acme\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/acme_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_keyfile: /var/www/acme2certifier/volume/acme/harica_staging_private_key.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_url: $ACME_URL\" >> data/volume/acme_srv.cfg\n          sudo echo \"account_path: /acct/\" >> data/volume/acme_srv.cfg\n          sudo echo \"acme_account_email: $ACME_ACCOUNT_EMAIL\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_kid: $EAB_KID\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_hmac_key: $EAB_HMAC_KEY\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.$DOMAIN\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" data/volume/acme_srv.cfg\n        env:\n          ACME_ACCOUNT_EMAIL: ${{ secrets.EMAIL }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/harica/acme_enroll\n        with:\n          DEPLOYMENT_TYPE: \"deb\"\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: acme-test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-legacy.yml",
    "content": "name: CA-Handler Tests - Backwards compatibility\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup a2c with legacy xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/volume/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n\n          # download legacy handler\n          curl https://codeload.github.com/grindsa/acme2certifier/tar.gz/refs/tags/0.31 -o /tmp/a2c.tgz\n          tar -xvzf /tmp/a2c.tgz -C /tmp/ --strip-components=3 acme2certifier-0.31/examples/ca_handler/xca_ca_handler.py\n          sudo cp /tmp/xca_ca_handler.py examples/Docker/data/xca_ca_handler.py\n          sudo chmod 777 examples/Docker/data/xca_ca_handler.py\n        shell: bash\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: Parse GitHub secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n\n      - name: \"Setup acme_srv.cfg with legacy xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/volume/acme_ca/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /opt/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          # download legacy handler\n          curl https://codeload.github.com/grindsa/acme2certifier/tar.gz/refs/tags/0.31 -o /tmp/a2c.tgz\n          tar -xvzf /tmp/a2c.tgz -C /tmp/ --strip-components=3 acme2certifier-0.31/examples/ca_handler/xca_ca_handler.py\n          sudo cp /tmp/xca_ca_handler.py data/volume/acme_ca/xca_ca_handler.py\n          sudo chmod 777 data/volume/acme_ca/xca_ca_handler.py\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup acme_srv.cfg with legacy xca_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777  data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n          # download legacy handler\n          curl https://codeload.github.com/grindsa/acme2certifier/tar.gz/refs/tags/0.31 -o /tmp/a2c.tgz\n          tar -xvzf /tmp/a2c.tgz -C /tmp/ --strip-components=3 acme2certifier-0.31/examples/ca_handler/openssl_ca_handler.py\n          sudo cp /tmp/openssl_ca_handler.py data/volume/acme_ca/openssl_ca_handler.py\n          sudo chmod 777 data/volume/acme_ca/openssl_ca_handler.py\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-msca.yml",
    "content": "name: CA-Handler Tests - MSCA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test mscertserv - container images (matrix from payload)\n  # ---------------------------------------------------------\n  mscertserv-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    name: \"mscertsrv_handler_tests\"\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: local\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"KRB Headerinfo - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"krb5_config: /var/www/acme2certifier/volume/krb5.conf\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo touch examples/Docker/data/krb5.conf\n          sudo chmod 777 examples/Docker/data/krb5.conf\n          cat <<EOF > examples/Docker/data/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          NAME_SPACE: local\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"KRB Headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"KRB ACME Profiling - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"krb5_config: /var/www/acme2certifier/volume/krb5.conf\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo touch examples/Docker/data/krb5.conf\n          sudo chmod 777 examples/Docker/data/krb5.conf\n          cat <<EOF > examples/Docker/data/krb5.conf\n          $KRB5_CONF\n          EOF\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"KRB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: container\n\n      - name: \"NTLM Headerinfo - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: ntlm\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"NTLM Headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM Headerinfo - Setup a2c with mscertsrv_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"NTLM Headerinfo - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM Headerinfo - Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"NTLM ACME Profiling - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: ntlm\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"NTLM ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: container\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp /etc/hosts ${{ github.workspace }}/artifact/data/\n          sudo cp /etc/resolv.conf ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data dnsmasq\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv-container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ----------------------------------------------------------------------\n  # Test mscertserv eab-profiling - container images (matrix from payload)\n  # ----------------------------------------------------------------------\n  mscertsrv-eabprov-container-tests:\n    name: \"mscertsrv-container-eab-profiling-tests\"\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: [guard, mscertserv-test-containers]\n    strategy:\n      fail-fast: false\n      # max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: local\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"EAB with headerinfo - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"krb5_config: /var/www/acme2certifier/volume/krb5.conf\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo touch examples/Docker/data/krb5.conf\n          sudo chmod 777 examples/Docker/data/krb5.conf\n          cat <<EOF > examples/Docker/data/krb5.conf\n          $KRB5_CONF\n          EOF\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          NAME_SPACE: local\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab\n        with:\n          NAME_SPACE: local\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"krb5_config: /var/www/acme2certifier/volume/krb5.conf\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo touch examples/Docker/data/krb5.conf\n          sudo chmod 777 examples/Docker/data/krb5.conf\n          cat <<EOF > examples/Docker/data/krb5.conf\n          $KRB5_CONF\n          EOF\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: container\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp /etc/hosts ${{ github.workspace }}/artifact/data/\n          sudo cp /etc/resolv.conf ${{ github.workspace }}/artifact/data/\n            cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data dnsmasq\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv-eabp-container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test mswcce - container images (matrix from payload)\n  # ---------------------------------------------------------\n  mswcce-test-containers:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      # max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"[ PREPARE ] get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_FQDN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_ADS_DOMAIN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n\n      - name: \"[ PREPARE ] test dns resulution\"\n        run: |\n          host $MSCA_ADS_DOMAIN 127.0.0.1\n          host $MSCA_FQDN 127.0.0.1\n          host $WES_HOST 127.0.0.1\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n\n      - name: \"NTLM Headerinfo - Setup a2c with ms_wcce_ca_handler\"\n        run: |\n          sudo cp .github/django_settings.py examples/Docker/data/settings.py\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ssh_host: $SSH_HOST:$SSH_PORT\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"NTLM Headerinfo - Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"NTLM Headerinfo  - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"NTLM ACME Profile - Setup a2c with ms_wcce_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ssh_host: $SSH_HOST:$SSH_PORT\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"NTLM ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: container\n\n      - name: \"KRB Headerinfo - Setup a2c with ms_wcce_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"KRB Headerinfo - Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"KRB Headerinfo - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"KRB - Setup a2c with mswcce_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"KRB - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"KRB ACME Profile - Setup a2c with ms_wcce_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"KRB ACME Profile - Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"KRB ACME Profile - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: container\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp dnsmasq/ ${{ github.workspace }}/artifact/dnsmasq/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data dnsmasq\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mswcce-container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test mswcce eab - container images (matrix from payload)\n  # ---------------------------------------------------------\n  mswcce-eab-container-tests:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: [guard, mswcce-test-containers]\n    strategy:\n      fail-fast: false\n      # max-parallel: 2\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"[ PREPARE ] get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_FQDN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_ADS_DOMAIN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n\n      - name: \"[ PREPARE ] test dns resulution\"\n        run: |\n          host $MSCA_ADS_DOMAIN 127.0.0.1\n          host $MSCA_FQDN 127.0.0.1\n          host $WES_HOST 127.0.0.1\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n\n      - name: \"EAB with headerinfo - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"EAB ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: container\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp dnsmasq/ ${{ github.workspace }}/artifact/dnsmasq/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data dnsmasq\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mswcce-eabp-container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # -------------------------------------------------------------------\n  # Test mswcce eab - container images container build without impacket\n  # -------------------------------------------------------------------\n  mscertsrv-without-impacket_tests:\n    name: \"mscertsrv-without-impacket_tests\"\n    runs-on: ubuntu-latest\n    needs: [guard, mswcce-eab-container-tests]\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      # max-parallel: 1\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Modify Dockerfile\"\n        run: |\n          cd examples/Docker/$WEBSRV/$DBHANDLER\n          sed -i '/RUN pip3 install impacket --break-system-packages && \\\\/d' Dockerfile\n          sed -i '/rm \\/usr\\/local\\/bin\\/\\*.py && \\\\/d' Dockerfile\n          sed -i '/rm -rf \\/usr\\/local\\/lib\\/python3.12\\/dist-packages\\/impacket\\/examples\\/\\* && \\\\/d' Dockerfile\n          sed -i \"s/    pip3 install -r/RUN pip3 install -r/g\" Dockerfile\n          sed -i \"s/    rm -rf \\/usr\\/local\\//\\#    rm -rf \\/usr\\/local\\//g\" Dockerfile\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          DBHANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Build container\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          NAME_SPACE: local\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"KRB - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/ca_certs.pem\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"krb5_config: /var/www/acme2certifier/volume/krb5.conf\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"verify: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n          sudo touch examples/Docker/data/krb5.conf\n          sudo chmod 777 examples/Docker/data/krb5.conf\n          cat <<EOF > examples/Docker/data/krb5.conf\n          $KRB5_CONF\n          EOF\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          VERIFY_CERT: \"false\"\n          USE_CERTBOT: \"false\"\n          TEST_ADL: \"false\"\n          NAME_SPACE: local\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv_wo_impacket_tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs mscertsrv\n  # ---------------------------------------------------------\n  mscertsrv-test-rpm:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        rhversion: [8]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          NAME_SPACE: \"local\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"KRB - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n          docker exec acme-srv yum install -y krb5-libs\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: ntlm\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"NTLM - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM - Setup a2c with mscertsrv_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"NTLM - Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/rpm_tester.sh restart\n\n      - name: \"NTLM - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n        with:\n          NAME_SPACE: local\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"KRB ACME Profiling - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: rpm\n          TAIL_NUMBER: 1900\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo rm -rf data/*.rpm\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          docker exec acme-srv ls -la /tmp > ${{ github.workspace }}/artifact/data/tmp_list\n          docker exec acme-srv ls -la /tmp\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv_handler_tests_rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs mscertsrv eab\n  # ---------------------------------------------------------\n  mscertsrv-eabprofile-test-rpm:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: [guard, mscertsrv-test-rpm]\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        rhversion: [9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          NAME_SPACE: \"local\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"EAB with headerinfo - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n          docker exec acme-srv yum install -y krb5-libs\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab\n        with:\n          NAME_SPACE: local\n\n      - name: \"EAB ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: rpm\n          TAIL_NUMBER: 1900\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo rm -rf data/*.rpm\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv ls -la /tmp > ${{ github.workspace }}/artifact/data/tmp_list\n          docker exec acme-srv ls -la /tmp\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv-eabprofile-test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs mswcce\n  # ---------------------------------------------------------\n  mswcce-tests-rpm:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        rhversion: [8]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse JSON secret - GH_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse JSON secret - SSH_TUNNEL_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          DJANGO_DB: psql\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          # sudo chmod -R 777 /etc/resolv.conf\n          # sudo echo \"nameserver 8.8.8.8\" > /etc/resolv.conf\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_FQDN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_ADS_DOMAIN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo sed -i \"s/ --local-service/ /g\" /etc/init.d/dnsmasq\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n\n      - name: \"Test dns resulution\"\n        run: |\n          host $MSCA_ADS_DOMAIN ${{ env.RUNNER_IP }}\n          host $MSCA_FQDN ${{ env.RUNNER_IP }}\n          host $WES_HOST 127.0.0.1\n\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n\n      - name: \"NTLM - Prepare acme_srv.cfg with ms_wcce_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"NTLM - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"KRB - Setup a2c with ms_wcce_ca_handler (Kerberos)\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"KRB - Setup a2c with mswcce_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"ACME Profiling - Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/rpm_tester.sh restart\n          docker exec acme-srv yum install -y krb5-libs\n\n      - name: \"ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: rpm\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo rm -rf data/*.rpm\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp dnsmasq/ ${{ github.workspace }}/artifact/dnsmasq/\n          # docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          # docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh dnsmasq\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mswcce-test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs mswcce eabprofile\n  # ---------------------------------------------------------\n  mswcce-eab-tests-rpm:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: [guard, mswcce-tests-rpm]\n    strategy:\n      max-parallel: 1\n      fail-fast: false\n      matrix:\n        rhversion: [9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse JSON secret - GH_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse JSON secret - SSH_TUNNEL_CFG\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          DJANGO_DB: psql\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          # sudo chmod -R 777 /etc/resolv.conf\n          # sudo echo \"nameserver 8.8.8.8\" > /etc/resolv.conf\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_FQDN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_ADS_DOMAIN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo sed -i \"s/ --local-service/ /g\" /etc/init.d/dnsmasq\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n\n      - name: \"Test dns resulution\"\n        run: |\n          host $MSCA_ADS_DOMAIN ${{ env.RUNNER_IP }}\n          host $MSCA_FQDN ${{ env.RUNNER_IP }}\n          host $WES_HOST 127.0.0.1\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n\n      - name: \"EAB with headerinfo - Setup a2c with ms_wcce_ca_handler (Kerberos)\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB with headerinfo - enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab\n\n      - name: \"EAB ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template\\\"\\: \\[\\\"WebServerModified\\\"\\, \\\"WebServer\\\"]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template\\\"\\: \\\"WebServerModified\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: rpm\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo rm -rf data/*.rpm\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp dnsmasq/ ${{ github.workspace }}/artifact/dnsmasq/\n          # docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          # docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh dnsmasq\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mswcce_eabprofiling-tests-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB mscertsrv\n  # ---------------------------------------------------------\n  mscertsrv-test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        # apache does not work for some reason\n        websrv: ['nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          NAME_SPACE: local\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n          NAME_SPACE: local\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NAME_SPACE: local\n\n      - name: \"KRB - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n          # docker exec acme-srv apt-get install -y python3-gssapi\n          docker exec acme-srv rm -rf /etc/krb5.conf\n          docker exec acme-srv ln -s /var/www/acme2certifier/volume/acme_ca/krb5.conf /etc/krb5.conf\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM - Setup a2c with mscertsrv_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: ntlm\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"NTLM - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n        with:\n          NAME_SPACE: local\n\n      - name: \"NTLM - Setup a2c with mscertsrv_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"NTLM - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n        with:\n          NAME_SPACE: local\n\n      - name: \"KRB ACME Profiling - Setup a2c with mscertsrv_ca_handler using kerberos\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: gssapi\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"krb5_config: volume/acme_ca/krb5.conf\" >> data/volume/acme_srv.cfg\n          sudo echo \"verify: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 30\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n          sudo touch data/volume/acme_ca/krb5.conf\n          sudo chmod 777 data/volume/acme_ca/krb5.conf\n          cat <<EOF > data/volume/acme_ca/krb5.conf\n          $KRB5_CONF\n          EOF\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          NAME_SPACE: local\n          DEPLOYMENT_TYPE: deb\n          TAIL_NUMBER: 1900\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mscertsrv-test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB mswcce\n  # ---------------------------------------------------------\n  mswcce-test-deb:\n    needs: [guard, mscertsrv-test-deb]\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse MSCA_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.MSCA_CFG }}\n\n      - name: \"Parse GH_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse SSH_TUNNEL_CFG secrets\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          # sudo chmod -R 777 /etc/resolv.conf\n          # sudo echo \"nameserver 8.8.8.8\" > /etc/resolv.conf\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_FQDN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$MSCA_ADS_DOMAIN/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo sed -i \"s/ --local-service/ /g\" /etc/init.d/dnsmasq\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n\n      - name: \"Test dns resulution\"\n        run: |\n          host $MSCA_ADS_DOMAIN ${{ env.RUNNER_IP }}\n          host $MSCA_FQDN ${{ env.RUNNER_IP }}\n          host $WES_HOST 127.0.0.1\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          MSCA_IP: ${{ env.MSCA_IP }}\n          MSCA_FQDN_WOTLD: ${{ env.MSCA_FQDN_WOTLD }}\n          MSCA_FQDN: ${{ env.MSCA_FQDN }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n\n      - name: \"NTLM - Prepare acme_srv.cfg with ms_wcce_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n          docker exec acme-srv apt-get install -y python3-impacket\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"NTLM - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"KRB - Setup a2c with ms_wcce_ca_handler (Kerberos)\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo touch data/volume/acme_ca/ca_certs.pem\n          sudo chmod 777 data/volume/acme_ca/ca_certs.pem\n          sudo echo \"$MSCA_CA_BUNDLE\" > data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment mit default profile and headerinfo\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_default_headerinfo\n\n      - name: \"KRB - Setup a2c with mswcce_ca_handler with allowed_domainlist configuration\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"foo1.bar\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"KRB - enrollment allowed domainlist\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_allowed_domain_list\n\n      - name: \"ACME Profiling - Setup a2c with ms_wcce_ca_handler (Kerboros)\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/mswcce_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $MSCA_FQDN\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $MSCA_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $MSCA_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $MSCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $MSCA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"target_domain: $MSCA_ADS_DOMAIN\" >> data/volume/acme_srv.cfg\n          sudo echo \"domain_controller: $RUNNER_IP\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"timeout: 20\" >> data/volume/acme_srv.cfg\n          sudo echo \"use_kerberos: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"WebServerModified\\\": \\\"http:\\/\\/foo.bar\\/acmeca1\\\", \\\"WebServer\\\": \\\"http:\\/\\/foo.bar\\/acmeca2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profiling - Enrollment\"\n        uses: ./.github/actions/wf_specific/ms_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: deb\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: mswcce-test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-nclm.yml",
    "content": "name: CA-Handler Tests - NCLM\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse NCLM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCLM_CFG }}\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/nclm_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NCLM_API_HOST: ${{ env.NCLM_API_HOST }}\n          NCLM_API_USER: ${{ env.NCLM_API_USER }}\n          NCLM_API_PASSWORD: ${{ env.NCLM_API_PASSWORD }}\n\n\n      - name: \"Setup a2c with nclm_ca_handler\"\n        run: |\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/nclm_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:4000\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCLM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCLM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"tsg_name: $NCLM_TSG_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCLM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_id_list: [$NCLM_CA_ID_LIST]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"request_timeout: 40\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 40/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          VERIFY_CERT: false\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Reconfigure nclm handler to test enrollment from MSCA\"\n        run: |\n          sudo sed -i \"s/ca_name: $NCLM_CA_NAME/ca_name: $NCLM_MSCA_NAME/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $NCLM_MSCA_TEMPLATE_NAME\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_RSA: true\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          mkdir -p ${{ github.workspace }}/artifact/clients\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp *.pem ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/clients/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/clients/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/clients/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data clients\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse NCLM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCLM_CFG }}\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/nclm_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NCLM_API_HOST: ${{ env.NCLM_API_HOST }}\n          NCLM_API_USER: ${{ env.NCLM_API_USER }}\n          NCLM_API_PASSWORD: ${{ env.NCLM_API_PASSWORD }}\n\n      - name: \"Setup a2c with with nclm_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/nclm_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $NCLM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCLM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCLM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"tsg_name: $NCLM_TSG_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCLM_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_id_list: [$NCLM_CA_ID_LIST]\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 40\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 60/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          VERIFY_CERT: false\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n\n      - name: \"Reconfigure nclm handler to test enrollment from MSCA\"\n        run: |\n          sudo sed -i \"s/ca_name: $NCLM_CA_NAME/ca_name: $NCLM_MSCA_NAME/g\" data/volume/acme_srv.cfg\n          sudo echo \"template_name: $NCLM_MSCA_TEMPLATE_NAME\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_RSA: true\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        continue-on-error: true\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          mkdir -p ${{ github.workspace }}/artifact/clients\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp *.pem ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/clients/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/clients/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/clients/lego/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data clients acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpms-rh${{ matrix.rhversion }}-${{ matrix.execscript}}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse NCLM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCLM_CFG }}\n\n      - name: \"Parse SSH tunnel configuration\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SSH_TUNNEL_CFG }}\n          uppercase: 'true'\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Setup tunnel\"\n        uses: ./.github/actions/wf_specific/nclm_ca_handler/tunnel_setup\n        with:\n          SSH_USER: ${{ env.SSH_USER }}\n          SSH_HOST: ${{ env.SSH_HOST }}\n          SSH_PORT: ${{ env.SSH_PORT }}\n          SSH_KNOWN_HOSTS: ${{ env.SSH_KNOWN_HOSTS }}\n          SSH_KEY: ${{ env.SSH_KEY }}\n          NCLM_API_HOST: ${{ env.NCLM_API_HOST }}\n          NCLM_API_USER: ${{ env.NCLM_API_USER }}\n          NCLM_API_PASSWORD: ${{ env.NCLM_API_PASSWORD }}\n\n      - name: \"Setup a2c with with nclm_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/nclm_ca_handler.py\" >> data/volume/acme_srv.cfg\n          # sudo echo \"api_host: $NCLM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: https://forwarder.acme:4000\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCLM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCLM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"tsg_name: $NCLM_TSG_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCLM_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_id_list: [$NCLM_CA_ID_LIST]\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"request_timeout: 40\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 60/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n          VERIFY_CERT: false\n          TEST_ADL: \"true\"\n\n      - name: \"Reconfigure nclm handler to test enrollment from MSCA\"\n        run: |\n          sudo sed -i \"s/ca_name: $NCLM_CA_NAME/ca_name: $NCLM_MSCA_NAME/g\" data/volume/acme_srv.cfg\n          sudo echo \"template_name: $NCLM_MSCA_TEMPLATE_NAME\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_RSA: true\n          HOSTNAME_SUFFIX: -${{ env.UUID }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-openssl.yml",
    "content": "name: CA-Handler Tests - OpenSSL\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup a2c with openssl_ca_handler - default\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Setup a2c with openssl_ca_handler - with template\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\nopenssl_conf: volume/acme_ca/openssl.cnf\" >> examples/Docker/data/acme_srv.cfg\n          sudo touch examples/Docker/data/acme_ca/openssl.cnf\n          sudo chmod 777 examples/Docker/data/acme_ca/openssl.cnf\n          sudo echo -e \"[extensions]\\nbasicConstraints = critical, CA:FALSE\\nsubjectKeyIdentifier = hash, issuer:always\\nauthorityKeyIdentifier = keyid:always, issuer:always\" >> examples/Docker/data/acme_ca/openssl.cnf\n          sudo echo -e \"keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement\\nextendedKeyUsage = critical, serverAuth, OCSPSigning\\n\" >> examples/Docker/data/acme_ca/openssl.cnf\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"With Tempßlate -  enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_w_teamplate\n\n      - name: \"Setup a2c with openssl_ca_handler - cn_enforce\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\ncn_enforce: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"With CN enforce - enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_cn_enforce\n\n      - name: \"Setup a2c with openssl_ca_handler - adjust cert_validity\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/cert_validity_days: 30/cert_validity_days: 3650\\ncert_validity_adjust: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"With cert_validity - enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_adjust_cert_validity\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup a2c with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Setup a2c with openssl_ca_handler - with template\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo -e \"\\nopenssl_conf: volume/acme_ca/openssl.cnf\" >> data/volume/acme_srv.cfg\n          sudo touch data/volume/acme_ca/openssl.cnf\n          sudo chmod 777 data/volume/acme_ca/openssl.cnf\n          sudo echo -e \"[extensions]\\nbasicConstraints = critical, CA:FALSE\\nsubjectKeyIdentifier = critical, hash, issuer:always\\nauthorityKeyIdentifier = keyid:always, issuer:always\" >> data/volume/acme_ca/openssl.cnf\n          sudo echo -e \"keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement\\nextendedKeyUsage = critical, serverAuth, OCSPSigning\\n\" >> data/volume/acme_ca/openssl.cnf\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"With Template -  enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_w_teamplate\n\n      - name: \"Setup a2c with openssl_ca_handler for django - cn_enforce\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo -e \"\\ncn_enforce: True\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"With CN enforce - enrollment\"\n        if: matrix.execscript == 'rpm_tester.sh'\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_cn_enforce\n\n      - name: \"Setup a2c with openssl_ca_handler for django - adjust cert_validity\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/cert_validity_days: 30/cert_validity_days: 3650\\ncert_validity_adjust: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"With cert_validity - enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_adjust_cert_validity\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Setup a2c with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Setup a2c with openssl_ca_handler - with template\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo -e \"\\nopenssl_conf: volume/acme_ca/openssl.cnf\" >> data/volume/acme_srv.cfg\n          sudo touch data/volume/acme_ca/openssl.cnf\n          sudo chmod 777 data/volume/acme_ca/openssl.cnf\n          sudo echo -e \"[extensions]\\nbasicConstraints = critical, CA:FALSE\\nsubjectKeyIdentifier = critical, hash, issuer:always\\nauthorityKeyIdentifier = keyid:always, issuer:always\" >> data/volume/acme_ca/openssl.cnf\n          sudo echo -e \"keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement\\nextendedKeyUsage = critical, serverAuth, OCSPSigning\\n\" >> data/volume/acme_ca/openssl.cnf\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"With Template -  enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_w_teamplate\n\n      - name: \"Setup a2c with openssl_ca_handler for django - cn_enforce\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo -e \"\\ncn_enforce: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"With CN enforce - enrollment\"\n        if: matrix.execscript == 'rpm_tester.sh'\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_cn_enforce\n\n      - name: \"Setup a2c with openssl_ca_handler for django - adjust cert_validity\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/cert_validity_days: 30/cert_validity_days: 3650\\ncert_validity_adjust: True/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"With cert_validity - enrollment\"\n        uses: ./.github/actions/wf_specific/openssl_ca_handler/enroll_adjust_cert_validity\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-openxpki.yml",
    "content": "name: CA-Handler Tests - OpenXPKI\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Instanciate OpenXPKI server\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/openxpki_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}/examples/Docker\n\n      - name: \"Setup a2c with est_ca_handler\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/est_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"est_host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_client_cert: volume/acme_ca/client_crt.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_client_key: volume/acme_ca/client_key.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n          sudo rm -rf  certbot/*\n\n      - name: \"Setup a2c with est_ca_handler using pksc12\"\n        run: |\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/est_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"est_host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_client_cert: volume/acme_ca/client_crt.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n          sudo rm -rf  certbot/*\n\n      - name: \"Setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"client_key: volume/acme_ca/client_key.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n          sudo rm -rf  certbot/*\n\n      - name: \"Reconfigure a2c (pkcs12 support)\"\n        run: |\n          docker ps -a\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n\n      - name: \"EAB ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.p12\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"tls-server\\\", \\\"tls-client\\\", \\\"profile_3\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"tls-client\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n          cd examples/Docker/\n          docker compose restart\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"container\"\n          REVOCATION: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker ps -a\n          docker compose logs > ${{ github.workspace }}/artifact/a2c.log\n          docker exec  OpenXPKI_Server cat /var/log/openxpki-server/catchall.log >> ${{ github.workspace }}/artifact/openxpki_server.log\n          docker exec  OpenXPKI_Client cat /var/log/openxpki-client/est.log >> ${{ github.workspace }}/artifact/openxpki_client_est.log\n          docker exec  OpenXPKI_Client cat /var/log/openxpki-client/rpc.log >> ${{ github.workspace }}/artifact/openxpki_client_rpc.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz openxpki_server.log openxpki_client_est.log openxpki_client_rpc.log a2c.log data # acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Instanciate OpenXPKI server\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/openxpki_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}\n\n      - name: \"Setup a2c with est_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"est_host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_cert: /opt/acme2certifier/volume/acme_ca/client_crt.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_key: /opt/acme2certifier/volume/acme_ca/client_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Setup a2c with est_ca_handler (pkcs12)\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_cert: /opt/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /opt/acme2certifier/volume/acme_ca/client_crt.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_key: /opt/acme2certifier/volume/acme_ca/client_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Reconfigure a2c (pkcs12 support)\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /opt/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /opt/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"EAB ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"tls-server\\\", \\\"tls-client\\\", \\\"profile_3\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"tls-client\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n          REVOCATION: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          # docker logs openxpki-docker_openxpki-server_1 > ${{ github.workspace }}/artifact/openxpki_server.log\n          # docker logs openxpki-docker_openxpki-client_1 > ${{ github.workspace }}/artifact/openxpki_client.log\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv rpm -qa > ${{ github.workspace }}/artifact/data/packages.txt\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data openxpki_server.log openxpki_client.log acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Instanciate OpenXPKI server\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/openxpki_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}\n\n      - name: \"Setup a2c with est_ca_handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"est_host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_key: /var/www/acme2certifier/volume/acme_ca/client_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Setup a2c with est_ca_handler (pkcs12)\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n          USE_CERTBOT: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_key: /var/www/acme2certifier/volume/acme_ca/client_key.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"Reconfigure a2c (pkcs12 support)\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n          REVOCATION: \"false\"\n\n      - name: \"ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"deb\"\n\n      - name: \"EAB ACME Profiling - setup a2c with openxpki_ca_handler\"\n        run: |\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/openxpki_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: https://openxpki:8443\" >> data/volume/acme_srv.cfg\n          # sudo echo \"host: https://$OPENXPKI_IP:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"client_cert: /var/www/acme2certifier/volume/acme_ca/client_crt.p12\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_passphrase: Test1234\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/acme_ca/ca_bundle.pem\" >> data/volume/acme_srv.cfg\n          sudo echo \"cert_profile_name: tls-server\" >> data/volume/acme_srv.cfg\n          sudo echo \"endpoint_name: enroll\" >> data/volume/acme_srv.cfg\n          sudo echo \"polling_timeout: 60\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"tls-client\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"tls-server\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"cert_profile_name\\\"\\: \\[\\\"tls-server\\\", \\\"tls-client\\\", \\\"profile_3\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"cert_profile_name\\\"\\: \\\"tls-client\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.org/*.acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n        env:\n          OPENXPKI_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - Test enrollment\"\n        uses: ./.github/actions/wf_specific/openxpki_ca_handler/enroll_eab_acmeprofile\n        with:\n          DEPLOYMENT_TYPE: \"deb\"\n          REVOCATION: \"false\"\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  certbot/*\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-pkcs7soap.yml",
    "content": "name: CA-Handler Tests - PKCS7 SOAP\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  int-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Prepare SOAP server\"\n        run: |\n          sudo mkdir -p examples/Docker/data\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo touch examples/Docker/data/soap_srv.cfg\n          sudo chmod 777 examples/Docker/data/soap_srv.cfg\n          sudo echo \"[CAhandler]\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"xdb_file: /etc/soap-srv/xca/$XCA_DB_NAME\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"issuing_ca_key: $XCA_ISSUING_CA\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/soap_srv.cfg\n\n      - name: \"Build and start SOAP server\"\n        working-directory: examples/Docker/\n        run: |\n          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n          echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n          sudo apt update\n          sudo apt install -y docker-compose-plugin\n          sudo mv ../../.dockerignore ../../.dockerignore.acme\n          docker compose -f soap_srv.yml up -d\n          docker compose -f soap_srv.yml logs\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Setup a2c with pkcs7_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/acme2certifier.pem examples/Docker/data/acme2certifier.pem\n          sudo cp .github/acme2certifier_cert.pem examples/Docker/data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem examples/Docker/data/acme2certifier_key.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/sub-ca-key.pem examples/Docker/data/key.pem\n          sudo cp test/ca/sub-ca-cert.pem examples/Docker/data/cert.pem\n          sudo cp test/ca/certs.pem examples/Docker/data/ca_bundle.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/pkcs7_soap_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"soap_srv: http://soap-srv.acme:8888\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_cert: /var/www/acme2certifier/volume/cert.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_key: /var/www/acme2certifier/volume/key.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: Test1234\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profilename: foo\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          cat examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose -f soap_srv.yml logs > ${{ github.workspace }}/artifact/soap-srv.log\n          docker compose logs > ${{ github.workspace }}/artifact/a2c.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz a2c.log data soap-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: int-container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  ext-test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Prepare SOAP server\"\n        run: |\n          sudo mkdir -p examples/Docker/data\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo touch examples/Docker/data/soap_srv.cfg\n          sudo chmod 777 examples/Docker/data/soap_srv.cfg\n          sudo echo \"[CAhandler]\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"xdb_file: /etc/soap-srv/xca/$XCA_DB_NAME\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"issuing_ca_key: $XCA_ISSUING_CA\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/soap_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/soap_srv.cfg\n\n      - name: \"Build and start SOAP server\"\n        working-directory: examples/Docker/\n        run: |\n          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n          echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n          sudo apt update\n          sudo apt install -y docker-compose-plugin\n          sudo mv ../../.dockerignore ../../.dockerignore.acme\n          docker compose -f soap_srv.yml up -d\n          docker compose -f soap_srv.yml logs\n\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Setup a2c with pkcs7_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/acme2certifier.pem examples/Docker/data/acme2certifier.pem\n          sudo cp .github/acme2certifier_cert.pem examples/Docker/data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem examples/Docker/data/acme2certifier_key.pem\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo cp examples/soap/mock_signer.py examples/Docker/data/\n          sudo chmod 755 examples/Docker/data/mock_signer.py\n          sudo cp test/ca/sub-ca-key.pem examples/Docker/data/key.pem\n          sudo cp test/ca/sub-ca-cert.pem examples/Docker/data/cert.pem\n          sudo cp test/ca/certs.pem examples/Docker/data/ca_bundle.pem\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/pkcs7_soap_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"soap_srv: http://soap-srv.acme:8888\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_script: /var/www/acme2certifier/volume/mock_signer.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_alias: /var/www/acme2certifier/volume/cert.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_config_variant: /var/www/acme2certifier/volume/key.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"signing_csr_path: /var/www/acme2certifier/volume\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: /var/www/acme2certifier/volume/ca_bundle.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"profilename: foo\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"email: grindsa@foo.bar\" >> examples/Docker/data/acme_srv.cfg\n          cat examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          REVOCATION: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose -f soap_srv.yml logs > ${{ github.workspace }}/artifact/soap-srv.log\n          docker compose logs > ${{ github.workspace }}/artifact/a2c.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz a2c.log data soap-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: ext-test-containers.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-vault.yml",
    "content": "name: CA-Handler Tests - Hashicorp Vault\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is ${{ env.RUNNER_IP }}\"\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Instanciate vault\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/vault_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}/examples/Docker\n\n      - name: \"Setup a2c with vault_ca_handler\"\n        run: |\n          mkdir -p examples/Docker/data/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/vault_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"vault_url: https://vault.acme:8200\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"vault_path: pki_int\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"vault_role: serverauth\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"vault_token: $VAULT_TOKEN\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          # sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" examples/Docker/data/acme_srv.cfg\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_CERTBOT: \"true\"\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"serverauth\\\": \\\"http:\\/\\/foo.bar\\/serverauth\\\", \\\"clientauth\\\": \\\"http:\\/\\/foo.bar\\/clientauth\\\", \\\"bar-dot-local\\\": \\\"http:\\/\\/foo.bar\\/bar-dot-local\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo echo -e \"\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"vault_role\\\"\\: \\[\\\"clientauth\\\", \\\"serverauth\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"vault_role\\\"\\: \\\"servers\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"vault_path\\\": \\\"pki\\\"/\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '18,19d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          # sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data docker-compose.log # acme-sh lego # certbot\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is ${{ env.RUNNER_IP }}\"\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Instanciate vault\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/vault_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}/examples/Docker\n\n      - name: \"Setup a2c with vault_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/vault_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_url: https://vault.acme:8200\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_path: pki_int\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_role: serverauth\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_token: $VAULT_TOKEN\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          # sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          # sudo cat data/volume/acme_srv.cfg\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n          docker ps -a\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_CERTBOT: \"true\"\n          TEST_ADL: \"true\"\n\n      - name: \"ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"serverauth\\\": \\\"http:\\/\\/foo.bar\\/serverauth\\\", \\\"clientauth\\\": \\\"http:\\/\\/foo.bar\\/clientauth\\\", \\\"bar-dot-local\\\": \\\"http:\\/\\/foo.bar\\/bar-dot-local\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"vault_role\\\"\\: \\[\\\"clientauth\\\", \\\"serverauth\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"vault_role\\\"\\: \\\"servers\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"vault_path\\\": \\\"pki\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-{{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Instanciate vault\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/vault_prep\n        with:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WORKING_DIR: ${{ github.workspace }}/examples/Docker\n\n      - name: \"Setup a2c with vault_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/vault_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_url: https://vault.acme:8200\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_path: pki_int\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_role: serverauth\" >> data/volume/acme_srv.cfg\n          sudo echo \"vault_token: $VAULT_TOKEN\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"enrollment_config_log: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          # sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout:15/g\" data/volume/acme_srv.cfg\n          # sudo cat data/volume/acme_srv.cfg\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          USE_CERTBOT: \"true\"\n          TEST_ADL: \"true\"\n\n      - name: \"ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"serverauth\\\": \\\"http:\\/\\/foo.bar\\/serverauth\\\", \\\"clientauth\\\": \\\"http:\\/\\/foo.bar\\/clientauth\\\", \\\"bar-dot-local\\\": \\\"http:\\/\\/foo.bar\\/bar-dot-local\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          VAULT_TOKEN: ${{ env.VAULT_TOKEN }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/vault_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profiling - setup a2c with vault_ca_handler\"\n        run: |\n          sudo echo -e \"\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"vault_role\\\"\\: \\[\\\"clientauth\\\", \\\"serverauth\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"vault_role\\\"\\: \\\"servers\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca_2\\\",/\\\"vault_path\\\": \\\"pki\\\"/\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown\\\": \\\"unknown\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '18,19d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/cahandler-xca.yml",
    "content": "name: CA-Handler Tests - XCA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"No template - Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          cd examples/Docker\n          docker compose logs | grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration'\n\n      - name: \"No Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_no_template\n\n      - name: \"Template - Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_template\n\n      - name: \"Header-info - Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Header-info - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_headerinfo\n\n      - name: \"EAB - Setup a2c with xca_ca_handler - profiling\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '22,24d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab\n\n      - name: \"EAB subject profiling - Setup a2c with xca_ca_handler \"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\\"acme\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\",/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '22,24d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '9d' examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"api_user\\\"\\: \\\"api_user\\\",/\\\"subject\\\"\\: \\{\\n        \\\"serialNumber\\\"\\: \\\"*\\\",\\n        \\\"organizationName\\\"\\: \\\"acme corp\\\",\\n        \\\"organizationalUnitName\\\"\\: \\[\\\"acme1\\\", \\\"acme2\\\"\\],\\n        \\\"countryName\\\"\\: \\\"AC\\\"\\n       \\}/g\" examples/Docker/data/kid_profiles.json\n\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB subject profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_sp\n\n      - name: \"ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '22,24d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,9d' examples/Docker/data/kid_profiles.json\n\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"No template - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          # sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Verify allowed_domainlist error\"\n        run: |\n          docker exec acme-srv grep -iE 'FQDN/SAN [^ ]+ not allowed by configuration' /var/log/messages\n\n      - name: \"No Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_no_template\n\n      - name: \"Template - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_template\n\n      - name: \"Header-info - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Header-info - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_headerinfo\n\n      - name: \"EAB - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab\n\n      - name: \"EAB subject profiling - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\",/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '9d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"api_user\\\"\\: \\\"api_user\\\",/\\\"subject\\\"\\: \\{\\n        \\\"serialNumber\\\"\\: \\\"*\\\",\\n        \\\"organizationName\\\"\\: \\\"acme corp\\\",\\n        \\\"organizationalUnitName\\\"\\: \\[\\\"acme1\\\", \\\"acme2\\\"\\],\\n        \\\"countryName\\\"\\: \\\"AC\\\"\\n       \\}/g\" data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB subject profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_sp\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"No template - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          # sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"No Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_no_template\n\n      - name: \"Template - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Template - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_template\n\n      - name: \"Header-info - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Header-info - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_headerinfo\n\n      - name: \"EAB - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab\n\n      - name: \"EAB subject profiling - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\",/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '9d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"api_user\\\"\\: \\\"api_user\\\",/\\\"subject\\\"\\: \\{\\n        \\\"serialNumber\\\"\\: \\\"*\\\",\\n        \\\"organizationName\\\"\\: \\\"acme corp\\\",\\n        \\\"organizationalUnitName\\\"\\: \\[\\\"acme1\\\", \\\"acme2\\\"\\],\\n        \\\"countryName\\\"\\: \\\"AC\\\"\\n       \\}/g\" data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB subject profiling - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_sp\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_acmeprofile\n\n      - name: \"EAB ACME Profile - Setup a2c with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nprofiles={\\\"acme\\\": \\\"http:\\/\\/foo.bar\\/profile1\\\", \\\"template\\\": \\\"http:\\/\\/foo.bar\\/profile2\\\", \\\"profile3\\\": \\\"http:\\/\\/foo.bar\\/profile3\\\"}/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \"challenge\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '22,24d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,9d' data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB ACME Profile - enrollment\"\n        uses: ./.github/actions/wf_specific/xca_ca_handler/enroll_eab_acmeprofile\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deployment-arm.yml",
    "content": "name: Deployment Tests - arm64\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  instance_start:\n    name: instance_start\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier' && inputs.branch == 'devel'\n    steps:\n\n    - name: \"checkout GIT\"\n      uses: actions/checkout@v6\n\n    - name: \"Parse AWS_CFG JSON secret\"\n      uses: ./.github/actions/parse-json-secret\n      with:\n        json_secret: ${{ secrets.AWS_CFG }}\n\n    - name: \"install awccli\"\n      run: |\n        sudo apt-get update\n        pip3 install awscli --upgrade --user\n        pip3 install boto3 --upgrade --user\n        export PATH=$PATH:$HOME/.local/bin\n\n    - name: \"configure awccli\"\n      run: |\n        aws --version\n        aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID\n        aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY\n        aws configure set default.region $AWS_REGION\n      env:\n        AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}\n        AWS_REGION: ${{ env.AWS_REGION }}\n\n    - name: \"check instance status\"\n      run: |\n        wget https://raw.githubusercontent.com/grindsa/aws_ec2_mgr/main/aws_ec_mgr.py\n        chmod a+rx ./aws_ec_mgr.py\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID | grep -i \"stopped\"\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n    - name: \"start instance\"\n      run: |\n        python3 ./aws_ec_mgr.py -a start -r $AWS_REGION -i $AWS_INSTANCE_ID\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n    - name: \"[ WAIT ] Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"check instance status\"\n      run: |\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID | grep -i \"running\"\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n  build_test:\n    name: build_test\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier' && inputs.branch == 'devel'\n    needs: instance_start\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n    - name: \"checkout GIT\"\n      uses: actions/checkout@v6\n\n    - name: \"Parse AWS_CFG JSON secret\"\n      uses: ./.github/actions/parse-json-secret\n      with:\n        json_secret: ${{ secrets.AWS_CFG }}\n\n    - name: \"Retrieve Version from version.py\"\n      run: |\n        echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n        echo UUID=$(uuidgen) >> $GITHUB_ENV\n\n    - run: echo \"Repo is at version ${{ steps.acme2certifier_ver.outputs.tag }}\"\n    - run: echo \"UUID ${{ env.UUID }}\"\n\n    - name: \"Prepare ssh environment in ramdisk\"\n      run: |\n        sudo mkdir -p /tmp/rd\n        sudo mount -t tmpfs -o size=5M none /tmp/rd\n        sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n        sudo chmod 600 /tmp/rd/ak.tmp\n        sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n      env:\n        SSH_KEY: ${{ env.AWS_SSH_KEY }}\n        KNOWN_HOSTS: ${{ env.AWS_SSH_KNOWN_HOSTS }}\n\n    - name: Set up QEMU\n      uses: docker/setup-qemu-action@v3\n      with:\n        platforms: 'arm64'\n\n    - uses: docker/setup-buildx-action@v3\n      with:\n        version: latest\n        buildkitd-flags: --debug\n\n    - name: Build\n      uses: docker/build-push-action@v5\n      with:\n        load: true\n        tags: grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }}-${{ env.UUID }}\n        file: examples/Docker/${{ matrix.websrv }}/${{ matrix.dbhandler }}/Dockerfile\n        platforms: linux/arm64\n\n    - name: \"Check if image is built\"\n      run: |\n        docker image save -o /tmp/a2c-image-$WEB_SRV-$DB_HANDLER.tar grindsa/acme2certifier:$WEB_SRV-$DB_HANDLER-$UUID\n        ls -la /tmp/a2c-image-$WEB_SRV-$DB_HANDLER.tar\n      env:\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Compress image\"\n      run: |\n        gzip /tmp/a2c-image-$WEB_SRV-$DB_HANDLER.tar\n        ls -la /tmp/a2c-image-$WEB_SRV-$DB_HANDLER.tar.gz\n      env:\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n\n    - name: \"Create working directory on remote host\"\n      run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts  mkdir -p /tmp/a2c/$UUID\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Copy image to remote host\"\n      run: sudo scp -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts /tmp/a2c-image-$WEB_SRV-$DB_HANDLER.tar.gz $SSH_USER@$SSH_HOST:/tmp/a2c/$UUID/\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Unpack image on remote host\"\n      run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts  gunzip /tmp/a2c/$UUID/a2c-image-$WEB_SRV-$DB_HANDLER.tar.gz\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Load image on remote host\"\n      run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts  \"docker load < /tmp/a2c/$UUID/a2c-image-$WEB_SRV-$DB_HANDLER.tar\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Generate keys and certificates\"\n      uses: ./.github/actions/cert_gen\n\n    - name: \"Prepare and data package\"\n      run: |\n        sudo mkdir -p /tmp/data/acme_ca/certs\n        sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /tmp/data/acme_ca/\n        sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /tmp/data/acme_srv.cfg\n        sudo cp .github/acme2certifier.pem /tmp/data/acme2certifier.pem\n        sudo cp .github/django_settings.py /tmp/data/settings.py\n        sudo cp .github/acme2certifier_cert.pem /tmp/data/acme2certifier_cert.pem\n        sudo cp .github/acme2certifier_key.pem /tmp/data/acme2certifier_key.pem\n\n    - name: \"Copy data package to remote host\"\n      run: sudo scp -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts -r /tmp/data $SSH_USER@$SSH_HOST:/tmp/a2c/$UUID/\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - run: echo \"Image name - grindsa/acme2certifier:$WEB_SRV-$DB_HANDLER-$UUID\"\n      env:\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Start container on remote host\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker network create $UUID\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -d --rm -id --platform linux/arm64 --network $UUID --name=acme-srv-$UUID -v /tmp/a2c/$UUID/data:/var/www/acme2certifier/volume/ grindsa/acme2certifier:$WEB_SRV-$DB_HANDLER-$UUID\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Sleep for 5s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 5s\n\n    - name: \"Test http://acme-srv/directory internally\"\n      run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --network $UUID curlimages/curl -f http://acme-srv-$UUID/directory\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Test if https://acme-srv/directory internally\"\n      run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --network $UUID curlimages/curl --insecure -f https://acme-srv-$UUID/directory\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"acme.sh enroll\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker pull neilpang/acme.sh:latest\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/acme-sh\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run --rm -id -v /tmp/a2c/$UUID/acme-sh:/acme.sh --network $UUID --name=acme-sh-$UUID neilpang/acme.sh:latest daemon\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker exec -i acme-sh-$UUID acme.sh --server http://acme-srv-$UUID  --accountemail 'acme-sh@example.com' --issue -d acme-sh-$UUID --standalone --debug 3 --output-insecure --force\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"acme.sh revoke\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker exec -i acme-sh-$UUID acme.sh --server http://acme-srv-$UUID --revoke -d acme-sh-$UUID --standalone --debug 3 --output-insecure\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Certbot enroll\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker pull certbot/certbot\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/certbot\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv-$UUID --no-eff-email\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv-$UUID --standalone --preferred-challenges http -d certbot-$UUID --cert-name certbot-$UUID\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Certbot revoke\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv-$UUID -d certbot-$UUID --cert-name certbot-$UUID\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Lego enroll\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker pull goacme/lego\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/lego\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i -v /tmp/a2c/$UUID/lego:/.lego/ --rm --name lego-$UUID --network $UUID goacme/lego -s https://acme-srv-$UUID/directory --tls-skip-verify -a --email lego@example.com -d lego-$UUID --http run\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Lego revoke\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i -v /tmp/a2c/$UUID/lego:/.lego/ --rm --name lego-$UUID  --network $UUID goacme/lego -s https://acme-srv-$UUID --tls-skip-verify -a --email \"lego@example.com\" -d lego-$UUID revoke\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        UUID: ${{ env.UUID }}\n\n    - name: \"Cleanup on remote host\"\n      run: |\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker stop acme-sh-$UUID\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker stop acme-srv-$UUID\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker network rm $UUID\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker image rm grindsa/acme2certifier:$WEB_SRV-$DB_HANDLER-$UUID\"\n        sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"sudo rm -rf /tmp/a2c/$UUID\"\n      env:\n        SSH_USER: ${{ env.AWS_SSH_USER }}\n        SSH_HOST: ${{ env.AWS_SSH_HOST }}\n        WEB_SRV: ${{ matrix.websrv }}\n        DB_HANDLER: ${{ matrix.dbhandler }}\n        UUID: ${{ env.UUID }}\n\n  instance_stop:\n    name: instance_stop\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier' && inputs.branch == 'devel'\n    needs: build_test\n    steps:\n\n    - name: \"checkout GIT\"\n      uses: actions/checkout@v6\n\n    - name: \"Parse AWS_CFG JSON secret\"\n      uses: ./.github/actions/parse-json-secret\n      with:\n        json_secret: ${{ secrets.AWS_CFG }}\n\n    - name: \"install awccli\"\n      run: |\n        sudo apt-get update\n        pip3 install awscli --upgrade --user\n        pip3 install boto3 --upgrade --user\n        export PATH=$PATH:$HOME/.local/bin\n\n    - name: \"configure awccli\"\n      run: |\n        aws --version\n        aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID\n        aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY\n        aws configure set default.region $AWS_REGION\n      env:\n        AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}\n        AWS_REGION: ${{ env.AWS_REGION }}\n\n    - name: \"stop instance\"\n      run: |\n        wget https://raw.githubusercontent.com/grindsa/aws_ec2_mgr/main/aws_ec_mgr.py\n        chmod a+rx ./aws_ec_mgr.py\n        python3 ./aws_ec_mgr.py -a stop -r $AWS_REGION -i $AWS_INSTANCE_ID\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n"
  },
  {
    "path": ".github/workflows/deployment-django.yml",
    "content": "name: Deyployment Tests - Django\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['django']\n        database: ['mariadb', 'psql', 'mssql']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n          DJANGO_DB: ${{ matrix.database }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo chmod 777 data/acme_srv.cfg\n          sudo echo \"\" >> data/acme_srv.cfg\n          sudo echo \"[Directory]\" >> data/acme_srv.cfg\n          sudo echo \"url_prefix: /foo\" >> data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: \"django\"\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        continue-on-error: true\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-container-${{ matrix.websrv }}-${{ matrix.database }}.db.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['django_tester.sh']\n        database: ['mariadb', 'psql', 'sqlite3', 'mssql']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          DJANGO_DB: ${{ matrix.database }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        continue-on-error: true\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        # nginx is not working with sqlite3 and mariadb\n        # websrv: ['apache2', 'nginx']\n        websrv: ['apache2']\n        execscript: ['django_tester.sh']\n        database: ['mariadb', 'psql', 'sqlite3', 'mssql']\n\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: ${{ matrix.database }}\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.database }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deployment-ha.yml",
    "content": "name: Deployment Tests - HA\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Setup environment\"\n        run: |\n          docker network create acme\n          sudo mkdir -p data/nginx\n          sudo chmod -R 777 data\n          # sudo cp acme2certifier-$RUN_ID.noarch.rpm data\n          sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n          # sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          sudo mkdir -p $PWD/lego\n          sudo mkdir -p $PWD/certbot\n          sudo mkdir -p $PWD/acme-sh\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Bring up Almalinux instance\"\n        run: |\n          docker run -d -id --rm --privileged --network acme --name=alma9-c1 -v \"$(pwd)/data\":/tmp/acme2certifier almalinux/9-init\n          docker run -d -id --rm --privileged --network acme --name=alma9-c2 -v \"$(pwd)/data\":/tmp/acme2certifier almalinux/9-init\n\n      - name: \"Prepare almalinux instances\"\n        run: |\n          docker exec alma9-c1 yum -y install epel-release\n          docker exec alma9-c1 yum -y install openssh-server openssh-clients procps syslog-ng\n          docker exec alma9-c1 sed -i \"s#UsePAM yes#UsePAM no#g\" /etc/ssh/sshd_config.d/50-redhat.conf\n          docker exec alma9-c1 systemctl enable sshd\n          docker exec alma9-c1 systemctl start sshd\n          docker exec alma9-c1 systemctl start syslog-ng\n          docker exec alma9-c2 yum install -y epel-release\n          docker exec alma9-c2 yum -y install openssh-server openssh-clients procps syslog-ng\n          docker exec alma9-c2 sed -i \"s#UsePAM yes#UsePAM no#g\" /etc/ssh/sshd_config.d/50-redhat.conf\n          docker exec alma9-c2 systemctl enable sshd\n          docker exec alma9-c2 systemctl start syslog-ng\n          docker exec alma9-c2 systemctl start sshd\n\n      - name: \"Prepare ssh users\"\n        run: |\n          sudo ssh-keygen -t rsa -N '' -f data/id_lsyncd_alma9-c1\n          sudo ssh-keygen -t rsa -N '' -f data/id_lsyncd_alma9-c2\n\n          # docker exec alma9-c1 rm /run/nologin\n          docker exec alma9-c1 mkdir -p /root/.ssh\n          docker exec alma9-c1 chmod 700 /root/.ssh\n          docker exec alma9-c1 cp /tmp/acme2certifier/id_lsyncd_alma9-c1 /root/.ssh/id_lsyncd\n          docker exec alma9-c1 cp /tmp/acme2certifier/id_lsyncd_alma9-c1.pub /root/.ssh/id_lsyncd.pub\n          docker exec alma9-c1 cp /tmp/acme2certifier/id_lsyncd_alma9-c2.pub /root/.ssh/authorized_keys\n          docker exec alma9-c1 chmod 600 /root/.ssh/id_lsyncd\n          docker exec alma9-c1 chmod 600 /root/.ssh/authorized_keys\n\n          # docker exec alma9-c2 rm /run/nologin\n          docker exec alma9-c2 mkdir -p /root/.ssh\n          docker exec alma9-c2 chmod 700 /root/.ssh\n          docker exec alma9-c2 cp /tmp/acme2certifier/id_lsyncd_alma9-c2 /root/.ssh/id_lsyncd\n          docker exec alma9-c2 cp /tmp/acme2certifier/id_lsyncd_alma9-c2.pub /root/.ssh/id_lsyncd.pub\n          docker exec alma9-c2 cp /tmp/acme2certifier/id_lsyncd_alma9-c1.pub /root/.ssh/authorized_keys\n          docker exec alma9-c2 chmod 600 /root/.ssh/id_lsyncd\n          docker exec alma9-c2 chmod 600 /root/.ssh/authorized_keys\n\n      - name: \"Configure mariadb on alma9-c1\"\n        run: |\n          docker exec alma9-c1 yum install -y mariadb-server\n          docker exec alma9-c1 systemctl enable mariadb\n          docker exec alma9-c1 sed -i \"s#pid-file=/run/mariadb/mariadb.pid#pid-file=/run/mariadb/mariadb.pid\\nserver-id = 1\\nreport_host = alma9-c1\\nlog_bin = /var/log/mariadb/mariadb-bin\\nlog_bin_index = /var/log/mariadb/mariadb-bin.index\\nrelay_log = /var/log/mariadb/relay-bin\\nrelay_log_index = /var/log/mariadb/relay-bin.index\\nlog-slave-updates\\nauto_increment_increment=2\\nauto_increment_offset=1#g\" /etc/my.cnf.d/mariadb-server.cnf\n          docker exec alma9-c1 systemctl restart mariadb\n\n          docker exec alma9-c1 mysql -u root -e\"CREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\"\n          docker exec alma9-c1 mysql -u root -e\"GRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\"\n          docker exec alma9-c1 mysql -u root -e\"FLUSH PRIVILEGES;\"\n\n          docker exec alma9-c1 mysql -u root -e\"SHOW MASTER STATUS\\G;\" | grep File | awk '{print $2}'\n          docker exec alma9-c1 mysql -u root -e\"SHOW MASTER STATUS\\G;\" | grep Position | awk '{print $2}'\n          echo FILE_NAME=$(docker exec alma9-c1 mysql -u root -e\"SHOW MASTER STATUS\\G;\" | grep File | awk '{print $2}') >> $GITHUB_ENV\n          echo POSITION=$(docker exec alma9-c1 mysql -u root -e\"SHOW MASTER STATUS\\G;\" | grep Position | awk '{print $2}') >> $GITHUB_ENV\n\n      - run: echo \"FILE_NAME is ${{ env.FILE_NAME }}\"\n      - run: echo \"POSITION tag is ${{ env.POSITION }}\"\n\n      - name: \"Configure mariadb on alma9-c2\"\n        run: |\n          docker exec alma9-c2 yum install -y mariadb-server\n          docker exec alma9-c2 systemctl enable mariadb\n          docker exec alma9-c2 sed -i \"s#pid-file=/run/mariadb/mariadb.pid#pid-file=/run/mariadb/mariadb.pid\\nserver-id = 2\\nreport_host = alma9-c2\\nlog_bin = /var/log/mariadb/mariadb-bin\\nlog_bin_index = /var/log/mariadb/mariadb-bin.index\\nrelay_log = /var/log/mariadb/relay-bin\\nrelay_log_index = /var/log/mariadb/relay-bin.index\\nlog-slave-updates\\nauto_increment_increment=2\\nauto_increment_offset=2#g\" /etc/my.cnf.d/mariadb-server.cnf\n          docker exec alma9-c2 systemctl restart mariadb\n\n          docker exec alma9-c2 mysql -u root -e\"CREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\"\n          docker exec alma9-c2 mysql -u root -e\"GRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\"\n          docker exec alma9-c2 mysql -u root -e\"FLUSH PRIVILEGES;\"\n\n      - name: \"Configure master-master replication on alma9-c2\"\n        run: |\n          docker exec alma9-c2 mysql -u root -e\"STOP SLAVE;\"\n          docker exec alma9-c2 mysql -u root -e\"CHANGE MASTER TO MASTER_HOST='alma9-c1.acme', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='$FILE_NAME', MASTER_LOG_POS=$POSITION;\"\n          docker exec alma9-c2 mysql -u root -e\"START SLAVE;\"\n        env:\n          FILE_NAME: ${{ env.FILE_NAME }}\n          POSITION: ${{ env.POSITION }}\n\n      - name: \"Check replication status on alma9-c2\"\n        run: |\n          docker exec alma9-c2 mysql -u root -e\"SHOW SLAVE STATUS\\G;\"\n          docker exec alma9-c2 mysql -u root -e\"SHOW SLAVE STATUS\\G;\" | grep \"Slave_IO_Running: Yes\"\n          docker exec alma9-c2 mysql -u root -e\"SHOW SLAVE STATUS\\G;\" | grep \"Slave_SQL_Running: Yes\"\n\n      - name: \"Configure master-master replication on alma9-c1\"\n        run: |\n          docker exec alma9-c1 mysql -u root -e\"STOP SLAVE;\"\n          docker exec alma9-c1 mysql -u root -e\"CHANGE MASTER TO MASTER_HOST='alma9-c2.acme', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='$FILE_NAME', MASTER_LOG_POS=$POSITION;\"\n          docker exec alma9-c1 mysql -u root -e\"START SLAVE;\"\n        env:\n          FILE_NAME: ${{ env.FILE_NAME }}\n          POSITION: ${{ env.POSITION }}\n\n      - name: \"Check replication status on alma9-c1\"\n        run: |\n          docker exec alma9-c1 mysql -u root -e\"SHOW SLAVE STATUS\\G;\"\n          docker exec alma9-c1 mysql -u root -e\"SHOW SLAVE STATUS\\G;\" | grep \"Slave_IO_Running: Yes\"\n          docker exec alma9-c1 mysql -u root -e\"SHOW SLAVE STATUS\\G;\" | grep \"Slave_SQL_Running: Yes\"\n\n      - name: \"Test replication between cluster nodes\"\n        run: |\n          docker exec alma9-c1 mysql -u root -e\"CREATE DATABASE testdb CHARACTER SET UTF8;\"\n          sleep 3\n          docker exec alma9-c2 mysql -u root -e\"SHOW DATABASES;\" | grep testdb\n          docker exec alma9-c2 mysql -u root -e\"DROP DATABASE testdb;\"\n          sleep 3\n          docker exec alma9-c1 mysql -u root -e\"SHOW DATABASES;\" | grep testdb -vqz\n          docker exec alma9-c1 mysql -u root -e\"SHOW DATABASES;\"\n\n          # docker exec alma9-c1 mysql -u root -e\"CREATE DATABASE acme2certifier CHARACTER SET UTF8;\"\n          # docker exec alma9-c1 mysql -u root -e\"GRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY '1mmSvDFl';\"\n          # docker exec alma9-c1 mysql -u root -e\"FLUSH PRIVILEGES;\"\n\n      - name: \"Install Lcynd\"\n        run: |\n          docker exec alma9-c1 yum install -y lsyncd\n          docker exec alma9-c1 mkdir -p /opt/acme2certifier/volume\n          docker exec alma9-c2 yum install -y lsyncd\n          docker exec alma9-c2 mkdir -p /opt/acme2certifier/volume\n\n      - name: \"Configure Lcynd\"\n        run: |\n          sudo cat <<EOF > alma9-c1-lsyncd.conf\n          settings {\n            logfile = \"/var/log/lsyncd/lsyncd.log\",\n            statusFile = \"/var/log/lsyncd/lsyncd.status\",\n            statusInterval = 20,\n            nodaemon   = false\n          }\n\n          sync {\n            default.rsyncssh,\n            source = \"/opt/acme2certifier/volume/\",\n            host = \"alma9-c2\",\n            targetdir = \"/opt/acme2certifier/volume/\",\n            rsync = {\n              rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n              compress = true,\n              owner = true,\n              group = true,\n              archive = true\n          }\n          }\n          EOF\n\n          cat <<EOF > alma9-c2-lsyncd.conf\n          settings {\n            logfile = \"/var/log/lsyncd/lsyncd.log\",\n            statusFile = \"/var/log/lsyncd/lsyncd.status\",\n            statusInterval = 20,\n            nodaemon   = false\n          }\n\n          sync {\n            default.rsyncssh,\n            source = \"/opt/acme2certifier/volume/\",\n            host = \"alma9-c1\",\n            targetdir = \"/opt/acme2certifier/volume/\",\n            rsync = {\n              rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n              compress = true,\n              owner = true,\n              group = true,\n              archive = true\n          }\n          }\n          EOF\n          sudo mv alma*.conf data/\n          docker exec alma9-c1 cp /tmp/acme2certifier/alma9-c1-lsyncd.conf /etc/lsyncd.conf\n          docker exec alma9-c2 cp /tmp/acme2certifier/alma9-c2-lsyncd.conf /etc/lsyncd.conf\n\n          docker exec alma9-c1 systemctl restart lsyncd\n          docker exec alma9-c1 systemctl enable lsyncd\n          docker exec alma9-c2 systemctl restart lsyncd\n          docker exec alma9-c2 systemctl enable lsyncd\n\n      - name: \"Lsync - Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Test syncronisation between cluster nodes\"\n        run: |\n          docker exec alma9-c1 cp /tmp/acme2certifier/alma9-c1-lsyncd.conf /opt/acme2certifier/volume/lsycd_test.txt\n          sleep 20\n          docker exec alma9-c1 ls -la /opt/acme2certifier/volume/\n          docker exec alma9-c2 ls -la /opt/acme2certifier/volume/\n          docker exec alma9-c2 ls /opt/acme2certifier/volume/  | grep -i lsycd_test.txt\n          docker exec alma9-c2 rm /opt/acme2certifier/volume/lsycd_test.txt\n          sleep 20\n          docker exec alma9-c1 ls -la /opt/acme2certifier/volume/  | grep -i lsycd_test.txt -vqz\n\n      - name: \"Install acme2certifier\"\n        run: |\n          docker exec alma9-c1 yum install python3-mysqlclient python3-django4.2 python3-pyyaml -y\n          docker exec alma9-c1 yum localinstall -y /tmp/acme2certifier/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec alma9-c1 chown -R nginx /opt/acme2certifier/volume/\n          docker exec alma9-c1 cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d\n          docker exec alma9-c1 cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d\n          docker exec alma9-c1 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig\n\n          docker exec alma9-c1 mkdir -p /var/www/acme2certifier/volume/\n          docker exec alma9-c1 cp /tmp/acme2certifier/nginx/acme2certifier_cert.pem /var/www/acme2certifier/volume/\n          docker exec alma9-c1 cp /tmp/acme2certifier/nginx/acme2certifier_key.pem /var/www/acme2certifier/volume/\n          docker exec alma9-c1 cp /tmp/acme2certifier/nginx/acme2certifier_cert.pem /etc/nginx/\n          docker exec alma9-c1 cp /tmp/acme2certifier/nginx/acme2certifier_key.pem /etc/nginx/\n\n          docker exec alma9-c1 sh -c \"head -n 37 /etc/nginx/nginx.conf.orig > /etc/nginx/nginx.conf\"\n          docker exec alma9-c1 sh -c \"echo '}' >> /etc/nginx/nginx.conf\"\n          docker exec alma9-c1  cp /opt/acme2certifier/examples/db_handler/django_handler.py /opt/acme2certifier/acme_srv/db_handler.py\n          docker exec alma9-c1 sh -c \"cp -r /opt/acme2certifier/examples/django/* /opt/acme2certifier/\"\n          docker exec alma9-c1 rm /opt/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec alma9-c1 ln -s /opt/acme2certifier/volume/acme_srv.cfg  /opt/acme2certifier/acme_srv/\n          docker exec alma9-c1 systemctl enable acme2certifier\n          docker exec alma9-c1 systemctl start acme2certifier\n          docker exec alma9-c1 systemctl enable nginx\n          docker exec alma9-c1 systemctl start nginx\n\n          docker exec alma9-c2 yum install python3-mysqlclient python3-django4.2 python3-pyyaml -y\n          docker exec alma9-c2 yum localinstall -y /tmp/acme2certifier/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec alma9-c2 chown -R nginx /opt/acme2certifier/volume/\n          docker exec alma9-c2 cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d\n          docker exec alma9-c2 cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d\n          docker exec alma9-c2 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig\n\n          docker exec alma9-c2 mkdir -p /var/www/acme2certifier/volume/\n          docker exec alma9-c2 cp /tmp/acme2certifier/nginx/acme2certifier_cert.pem /var/www/acme2certifier/volume/\n          docker exec alma9-c2 cp /tmp/acme2certifier/nginx/acme2certifier_key.pem /var/www/acme2certifier/volume/\n          docker exec alma9-c2 cp /tmp/acme2certifier/nginx/acme2certifier_cert.pem /etc/nginx/\n          docker exec alma9-c2 cp /tmp/acme2certifier/nginx/acme2certifier_key.pem /etc/nginx/\n\n          docker exec alma9-c2 sh -c \"head -n 37 /etc/nginx/nginx.conf.orig > /etc/nginx/nginx.conf\"\n          docker exec alma9-c2 sh -c \"echo '}' >> /etc/nginx/nginx.conf\"\n          docker exec alma9-c2  cp /opt/acme2certifier/examples/db_handler/django_handler.py /opt/acme2certifier/acme_srv/db_handler.py\n          docker exec alma9-c2 sh -c \"cp -r /opt/acme2certifier/examples/django/* /opt/acme2certifier/\"\n          docker exec alma9-c2 rm /opt/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec alma9-c2 ln -s /opt/acme2certifier/volume/acme_srv.cfg  /opt/acme2certifier/acme_srv/\n          docker exec alma9-c2 systemctl enable acme2certifier\n          docker exec alma9-c2 systemctl start acme2certifier\n          docker exec alma9-c2 systemctl enable nginx\n          docker exec alma9-c2 systemctl start nginx\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n\n      - name: \"Prepare handler configuration\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s#volume/acme_ca/#/opt/acme2certifier/volume/acme_ca/#g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s#examples/ca_handler/#/opt/acme2certifier/examples/ca_handler/#g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          docker exec alma9-c1 sh -c \"cp -r /tmp/acme2certifier/volume/* /opt/acme2certifier/volume/\"\n          docker exec alma9-c1 chown -R nginx.nginx /opt/acme2certifier/volume/\n\n      - name: \"Profile ${{ secrets.ASA_PROFILE1 }} - Sleep for 20s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 20s\n\n      - name: \"Configure acme2certifier\"\n        run: |\n          docker exec alma9-c1 mysql -u root -e\"CREATE DATABASE acme2certifier CHARACTER SET UTF8;\"\n          docker exec alma9-c1 mysql -u root -e\"GRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY 'a2cpasswd';\"\n          docker exec alma9-c1 mysql -u root -e\"FLUSH PRIVILEGES;\"\n\n          sudo cp .github/django_settings_mariadb.py data/alma9-c1-settings.py\n          sudo sed -i \"s/mariadbsrv.acme/alma9-c1.acme/g\" data/alma9-c1-settings.py\n          sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/alma9-c1-settings.py\n          sudo sed -i \"s/\\\"PASSWORD\\\": \\\"1mmSvDFl\\\"/\\\"PASSWORD\\\": \\\"a2cpasswd\\\"/g\" data/alma9-c1-settings.py\n          docker exec alma9-c1 cp /tmp/acme2certifier/alma9-c1-settings.py /opt/acme2certifier/acme2certifier/settings.py\n          docker exec alma9-c1 sh -c \"cd /opt/acme2certifier/ && python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py loaddata acme_srv/fixture/status.yaml\"\n          docker exec alma9-c1 systemctl restart acme2certifier.service\n\n          sudo cp .github/django_settings_mariadb.py data/alma9-c2-settings.py\n          sudo sed -i \"s/mariadbsrv.acme/alma9-c2.acme/g\" data/alma9-c2-settings.py\n          sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/alma9-c2-settings.py\n          sudo sed -i \"s/\\\"PASSWORD\\\": \\\"1mmSvDFl\\\"/\\\"PASSWORD\\\": \\\"a2cpasswd\\\"/g\" data/alma9-c2-settings.py\n          docker exec alma9-c2 cp /tmp/acme2certifier/alma9-c2-settings.py /opt/acme2certifier/acme2certifier/settings.py\n          docker exec alma9-c2 systemctl restart acme2certifier.service\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test acme2certifier on alma9-c1\"\n        run: |\n          docker run -i --rm --network acme curlimages/curl -f https://alma9-c1.acme/directory --insecure\n          sudo rm -rf lego/\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://alma9-c1.acme -a --email \"lego@example.com\" -d lego.local --http run\n\n      - name: \"Test acme2certifier on alma9-c2\"\n        run: |\n          docker run -i --rm --network acme curlimages/curl -f https://alma9-c2.acme/directory --insecure\n          sudo rm -rf lego/\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://alma9-c2.acme -a --email \"lego@example.com\" -d lego.local --http run\n\n      - name: \"Setup and test load-balancer\"\n        run: |\n          docker run -d --rm --name acme-srv --network acme  grindsa/pen:latest -r 443 alma9-c1.acme:443 alma9-c2.acme:443\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test acme2certifier on load-balancer\"\n        run: |\n          docker run -i --rm --network acme curlimages/curl -f https://acme-srv.acme/directory --insecure\n\n      - name: \"Create script for mass-testing\"\n        run: |\n          cat <<EOF > data/mass_test.sh\n          #!/bin/bash\n\n          MAXCOUNTER=100\n          counter=1\n          echo \"## Start mass-test ##\"\n          until [ \\$counter -gt \\$MAXCOUNTER ]\n          do\n              echo \"## Counter \\${counter} ##\"\n              sudo rm -rf \\$PWD/acme-sh/*\n              sudo rm -rf \\$PWD/lego/*\n              sudo rm -rf \\$PWD/certbot/*\n              docker run -i -p 80:80 --rm --network acme --name=lego -v \\$PWD/lego:/.lego/ goacme/lego --tls-skip-verify -s https://acme-srv.acme -a --email \"lego@example.com\" -d lego01.acme --http run  > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n              docker run -i -p 80:80 --rm --network acme --name=acme-sh -v \\$PWD/acme-sh:/acme.sh neilpang/acme.sh:latest --issue --server https://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --insecure --force > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n              docker run -i -p 80:80 --rm --network acme --name=certbot -v \\$PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme  --no-verify-ssl --agree-tos -m 'certbot@example.com' --no-eff-email --standalone --preferred-challenges http -d certbot.acme --cert-name certbot --force-renewal > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n              docker run -i -p 80:80 --rm --network acme --name=lego -v \\$PWD/lego:/.lego/ goacme/lego --tls-skip-verify -s https://acme-srv.acme -a --email \"lego@example.com\" -d lego01.acme --http run > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n              docker run -i -p 80:80 --rm --network acme --name=acme-sh -v \\$PWD/acme-sh:/acme.sh neilpang/acme.sh:latest --issue --server https://acme-srv  --accountemail 'acme-sh@example.com' -d acme-sh.acme --standalone --output-insecure --insecure --force > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n              docker run -i -p 80:80 --rm --network acme --name=certbot -v \\$PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --no-verify-ssl --agree-tos -m 'certbot@example.com' --no-eff-email --standalone --preferred-challenges http -d certbot.acme --cert-name certbot  --force-renewal > /dev/null\n              if [[ $? != 0 ]]; then break; fi\n            ((counter++))\n\n          done\n          echo \"## End mass-test ##\"\n          echo \\$counter\n          echo \\$MAXCOUNTER\n          if [ \\$counter -gt \\$MAXCOUNTER ]\n          then\n              exit 0\n          else\n              exit 1\n          fi\n\n          EOF\n          chmod a+rx data/mass_test.sh\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Run mass-test\"\n        run: |\n          echo \"## Run mass-test ##\"\n          data/mass_test.sh\n          echo \"## End mass-test ##\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec alma9-c1 tar cvfz /tmp/acme2certifier/alma9-c1-a2c.tgz /opt/acme2certifier\n          docker exec alma9-c2 tar cvfz /tmp/acme2certifier/alma9-c2-a2c.tgz /opt/acme2certifier\n          docker exec alma9-c1 systemctl status nginx.service\n          docker exec alma9-c1 journalctl -xeu nginx.service\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          docker exec alma9-c1 cat /var/log/messages > ${{ github.workspace }}/artifact/alma9-c1-messages.log\n          docker exec alma9-c2 cat /var/log/messages > ${{ github.workspace }}/artifact/alma9-c2-messages.log\n          docker exec alma9-c1 cat /var/log/lsyncd/lsyncd.log > ${{ github.workspace }}/artifact/alma9-c1-lsyncd.log\n          docker exec alma9-c2 cat /var/log/lsyncd/lsyncd.log > ${{ github.workspace }}/artifact/alma9-c2-lsyncd.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data alma9-c1-messages.log alma9-c2-messages.log alma9-c1-lsyncd.log alma9-c2-lsyncd.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deployment-manual-install.yml",
    "content": "name: Deployment Tests - Manual Installation\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  ubuntu_manual_apache_wsgi:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: Branch name\n        run: echo running on branch ${GITHUB_REF##*/}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Run install script\"\n        run: |\n          sudo mkdir -p data\n          chmod a+rx examples/install_scripts/a2c-ubuntu22-apache2.sh\n          examples/install_scripts/a2c-ubuntu22-apache2.sh ${GITHUB_REF##*/}\n\n      - name: \"Local modification to get a2c running\"\n        run: |\n          sudo chmod a+w /etc/hosts\n          sudo echo \"$RUNNER_IP acme-srv\" >> /etc/hosts\n          sudo apt-get install -y socat\n          sudo sed -i \"s/Listen 80/Listen 8080/g\" /etc/apache2/ports.conf\n          sudo sed -i \"s/Listen 443/Listen 1443/g\" /etc/apache2/ports.conf\n          sudo sed -i \"s/*:80/*:8080/g\" /etc/apache2/sites-available/acme2certifier.conf\n          sudo sed -i \"s/*:443/*:1443/g\" /etc/apache2/sites-available/acme2certifier_ssl.conf\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/volume\\/acme_ca/\\/var\\/www\\/acme2certifier\\/volume\\/acme_ca/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo service apache2 restart\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Create Namespace\"\n        run: docker network create acme\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          ACME_SERVER: acme-srv\n          HTTP_PORT: 8080\n          HTTPS_PORT: 1443\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp /var/log/apache2 ${{ github.workspace }}/artifact/data/\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: ubuntu_manual_apache_wsgi.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  ubuntu_manual_nginx_wsgi:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: Branch name\n        run: echo running on branch ${GITHUB_REF##*/}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Run install script\"\n        run: |\n          sudo mkdir -p data\n          sh examples/install_scripts/a2c-ubuntu22-nginx.sh\n\n      - name: \"Local modification to get a2c running\"\n        run: |\n          sudo chmod a+w /etc/hosts\n          sudo echo \"$RUNNER_IP acme-srv\" >> /etc/hosts\n          sudo apt-get install -y socat\n          sudo sed -i \"s/listen 80/listen 8080/g\" /etc/nginx/sites-enabled/acme_srv.conf\n          sudo sed -i \"s/listen [::]:80/listen [::]:8080/g\" /etc/nginx/sites-enabled/acme_srv.conf\n          sudo sed -i \"s/listen 443/listen 1443/g\" /etc/nginx/sites-enabled/acme_srv_ssl.conf\n          sudo sed -i \"s/listen [::]:443/listen [::]:1443/g\" /etc/nginx/sites-enabled/acme_srv_ssl.conf\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/volume\\/acme_ca/\\/var\\/www\\/acme2certifier\\/volume\\/acme_ca/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo service nginx restart\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Create Namespace\"\n        run: docker network create acme\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          ACME_SERVER: acme-srv\n          HTTP_PORT: 8080\n          HTTPS_PORT: 1443\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp /var/log/apache2 ${{ github.workspace }}/artifact/data/\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: ubuntu_manual_nginx_wsgi.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  alma_manual_nginx_wsgi:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: Branch name\n        run: echo running on branch ${GITHUB_REF##*/}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          mkdir -p acme-sh\n          echo \"exit 0\" >> examples/install_scripts/a2c-centos9-nginx.sh\n\n      - name: \"Almalinux instance\"\n        run: |\n          docker run -d -id --rm --privileged --network acme --name=acme-srv -v \"$(pwd)/\":/tmp/acme2certifier almalinux/9-init\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/examples/Docker/almalinux-systemd/script_tester.sh\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: alma_nginx_wsgi.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  test-deb-apache2:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n\n      - name: Install apache2 and acme2certifier packages\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n          sudo apt-get install -y /tmp/acme2certifier-$RUN_ID-1_all.deb\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"configure a2c\"\n        run: |\n          sudo cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\n          sudo cp /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n          sudo a2enmod ssl\n          sudo a2ensite acme2certifier\n          sudo a2ensite acme2certifier_ssl\n          sudo mkdir -p /var/www/acme2certifier/volume/\n          sudo cp .github/acme2certifier.pem /var/www/acme2certifier/volume/\n          sudo rm /etc/apache2/sites-enabled/000-default.conf\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo mkdir -p /var/www/acme2certifier/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /var/www/acme2certifier/volume/acme_ca/\n          sudo chown -R www-data.www-data /var/www/acme2certifier/volume\n          sudo systemctl start apache2\n\n      - name: \"Modfiy configuration to allow certifiate enrollment\"\n        run: |\n          sudo chmod a+w /etc/hosts\n          sudo echo \"$RUNNER_IP acme-srv\" >> /etc/hosts\n          # sudo apt-get install -y socat\n          sudo sed -i \"s/Listen 80/Listen 8080/g\" /etc/apache2/ports.conf\n          sudo sed -i \"s/Listen 443/Listen 1443/g\" /etc/apache2/ports.conf\n          sudo sed -i \"s/*:80/*:8080/g\" /etc/apache2/sites-available/acme2certifier.conf\n          sudo sed -i \"s/*:443/*:1443/g\" /etc/apache2/sites-available/acme2certifier_ssl.conf\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/volume\\/acme_ca/\\/var\\/www\\/acme2certifier\\/volume\\/acme_ca/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo systemctl restart apache2\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Create Namespace\"\n        run: docker network create acme\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          ACME_SERVER: acme-srv\n          HTTP_PORT: 8080\n          HTTPS_PORT: 1443\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp /var/log/apache2 ${{ github.workspace }}/artifact/data/\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-apache2.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-deb-nginx:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Install nginx and acme2certifier packages\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3\n          sudo apt-get install -y /tmp/acme2certifier-$RUN_ID-1_all.deb\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Prepare local modification to get a2c running\"\n        run: |\n          sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv.conf\n          sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv_ssl.conf\n          sudo cp examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\n          sudo cp examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n          sudo rm /etc/nginx/sites-enabled/default\n          sudo ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n          sudo ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n          sudo mkdir -p /var/www/acme2certifier/volume/\n          sudo cp .github/acme2certifier_cert.pem /var/www/acme2certifier/volume/\n          sudo cp .github/acme2certifier_key.pem /var/www/acme2certifier/volume/\n          sudo chown -R www-data.www-data /var/www/acme2certifier/\n          sudo systemctl start nginx\n\n      - name: \"Modify uwsgi configuration file\"\n        run: |\n          sed -i \"s/\\/run\\/uwsgi\\/acme.sock/acme.sock/g\" examples/nginx/acme2certifier.ini\n          sed -i \"s/nginx/www-data/g\" examples/nginx/acme2certifier.ini\n          echo \"plugins=python3\" >> examples/nginx/acme2certifier.ini\n          sudo cp examples/nginx/acme2certifier.ini /var/www/acme2certifier\n\n      - name: \"Create a2c service\"\n        run: |\n          cat <<EOT > acme2certifier.service\n          [Unit]\n          Description=uWSGI instance to serve acme2certifier\n          After=network.target\n\n          [Service]\n          User=www-data\n          Group=www-data\n          WorkingDirectory=/var/www/acme2certifier\n          Environment=\"PATH=/var/www/acme2certifier\"\n          ExecStart=uwsgi --ini acme2certifier.ini\n\n          [Install]\n          WantedBy=multi-user.target\n          EOT\n\n          sudo cp acme2certifier.service /etc/systemd/system/acme2certifier.service\n          sudo systemctl start acme2certifier\n          sudo systemctl enable acme2certifier\n\n      - name: \"Configure ca_handler\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo mkdir -p /var/www/acme2certifier/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /var/www/acme2certifier/volume/acme_ca/\n          sudo chown -R www-data.www-data /var/www/acme2certifier/volume\n\n      - name: \"Modfiy configuration to allow certifiate enrollment\"\n        run: |\n          sudo chmod a+w /etc/hosts\n          sudo echo \"$RUNNER_IP acme-srv\" >> /etc/hosts\n          sudo sed -i \"s/listen 80/listen 8080/g\" /etc/nginx/sites-enabled/acme_srv.conf\n          sudo sed -i \"s/listen [::]:80/listen [::]:8080/g\" /etc/nginx/sites-enabled/acme_srv.conf\n          sudo sed -i \"s/listen 443/listen 1443/g\" /etc/nginx/sites-enabled/acme_srv_ssl.conf\n          sudo sed -i \"s/listen [::]:443/listen [::]:1443/g\" /etc/nginx/sites-enabled/acme_srv_ssl.conf\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/volume\\/acme_ca/\\/var\\/www\\/acme2certifier\\/volume\\/acme_ca/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\"  /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          sudo systemctl restart nginx\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: curl -f http://127.0.0.1:8080/directory\n\n      - name: \"Create Namespace\"\n        run: docker network create acme\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          ACME_SERVER: acme-srv\n          HTTP_PORT: 8080\n          HTTPS_PORT: 1443\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp /var/log/nginx ${{ github.workspace }}/artifact/data/\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-nginx.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-python-install:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        db_handler: ['wsgi', 'django']\n\n    steps:\n    - name: \"checkout GIT\"\n      uses: actions/checkout@v4\n\n    - name: \"Create Namespace\"\n      run: docker network create acme\n\n    - name: \"Prepare setup\"\n      uses: ./.github/actions/wf_specific/manual/setup\n      with:\n        DB_HANDLER: ${{ matrix.db_handler }}\n\n    - name: \"Test enrollment\"\n      uses: ./.github/actions/acme_clients\n      with:\n        ACME_SERVER: acme-srv\n        # HTTP_PORT: 8080\n        # HTTPS_PORT: 1443\n\n    - name: \"[ * ] Collecting test logs\"\n      if: ${{ failure() }}\n      run: |\n        mkdir -p ${{ github.workspace }}/artifact/upload\n        docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/lib/acme2certifier\n        docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n        docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n        cp a2c.tgz ${{ github.workspace }}/artifact/\n        sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme-srv.log syslog a2c.tgz\n\n    - name: \"[ * ] Uploading artifacts\"\n      uses: actions/upload-artifact@v4\n      if: ${{ failure() }}\n      with:\n        name: test-python-install-${{ matrix.db_handler }}.tar.gz\n        path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deployment-push-images-to-dockerhub.yml",
    "content": "name: Deployment Tests - Update images on dockerhub and ghcr.io\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' && inputs.branch == 'master' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  instance_start:\n    name: instance_start\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v6\n\n    - name: Parse AWS secrets from JSON\n      uses: ./.github/actions/parse-json-secret\n      with:\n        json_secret: ${{ secrets.AWS_CFG }}\n\n    - name: \"install awccli\"\n      run: |\n        sudo apt-get update\n        pip3 install awscli --upgrade --user\n        pip3 install boto3 --upgrade --user\n        export PATH=$PATH:$HOME/.local/bin\n\n    - name: \"configure awccli\"\n      run: |\n        aws --version\n        aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID\n        aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY\n        aws configure set default.region $AWS_REGION\n      env:\n        AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}\n        AWS_REGION: ${{ env.AWS_REGION }}\n\n    - name: \"check instance status\"\n      run: |\n        wget https://raw.githubusercontent.com/grindsa/aws_ec2_mgr/main/aws_ec_mgr.py\n        chmod a+rx ./aws_ec_mgr.py\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID | grep -i \"stopped\"\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n    - name: \"start instance\"\n      run: |\n        python3 ./aws_ec_mgr.py -a start -r $AWS_REGION -i $AWS_INSTANCE_ID\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n    - name: \"[ WAIT ] Sleep for 10s\"\n      uses: juliangruber/sleep-action@v2.0.3\n      with:\n        time: 10s\n\n    - name: \"check instance status\"\n      run: |\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID | grep -i \"running\"\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n\n  build_and_upload_images_to_hub:\n    name: Push images to dockerhub and github\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    needs: instance_start\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n      - name: \"Get current version\"\n        uses: oprypin/find-latest-tag@v1\n        with:\n          repository: ${{ github.repository }}  # The repository to scan.\n          releases-only: true  # We know that all relevant tags have a GitHub release for them.\n        id: acme2certifier_ver  # The step ID to refer to later.\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Parse software repository secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SWREPO_CFG }}\n\n      - name: \"Retrieve version from version.py\"\n        run: |\n          echo APP_NAME=$(echo \"$GITHUB_REPOSITORY\" | awk -F / '{print $2}') >> $GITHUB_ENV\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n          echo BUILD_NAME=${{ matrix.websrv }}-${{ matrix.dbhandler }} >> $GITHUB_ENV\n        env:\n          GITHUB_REPOSITORY: ${{ github.repository }}\n\n      - name: \"Retrieve 2nd last release tag\"\n        run: |\n          VERSION=$(echo ${{ env.TAG_NAME }} | awk -F. '{print $2}')\n          PRE_VERSION=$(($VERSION - 1))\n          echo $PRE_VERSION\n          for row in $(curl https://api.github.com/repos/grindsa/acme2certifier/tags | jq .[].name);\n            do\n                if [[ $row =~ $PRE_VERSION ]]; then\n                  echo OLD_TAG_NAME=$(echo $row | sed s/\\\"//g) >> $GITHUB_ENV\n                  echo $row\n                  break\n                fi\n            done\n\n      - name: \"Display version information\"\n        run: |\n          echo \"Repo is at version $ACME2CERTIFIER_VERSION\"\n          echo \"APP tag is $APP_NAME\"\n          echo \"Latest tag is $TAG_NAME\"\n          echo \"Old tag is $OLD_TAG_NAME\"\n          echo \"BUILD_NAME is $BUILD_NAME\"\n        env:\n          ACME2CERTIFIER_VERSION: ${{ steps.acme2certifier_ver.outputs.tag }}\n          APP_NAME: ${{ env.APP_NAME }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n          OLD_TAG_NAME: ${{ env.OLD_TAG_NAME }}\n          BUILD_NAME: ${{ env.BUILD_NAME }}\n\n      - name: Checkout code for 2nd last release\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ env.OLD_TAG_NAME }}\n\n      - name: \"show version from version.py\"\n        run: |\n          cat acme_srv/version.py\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: all\n\n      - uses: docker/setup-buildx-action@v3\n        with:\n          version: latest\n          buildkitd-flags: --debug\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ env.DOCKERHUB_USER }}\n          password: ${{ env.DOCKERHUB_TOKEN }}\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ env.GHCR_USER }}\n          password: ${{ env.GHCR_TOKEN }}\n\n      - name: Build with 2nd latest release tag\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: grindsa/acme2certifier:${{ env.OLD_TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}\n          file: examples/Docker/${{ matrix.websrv }}/${{ matrix.dbhandler }}/Dockerfile\n          platforms: linux/arm64, linux/amd64\n\n      - name: Push image to GHCR\n        run: |\n          docker buildx imagetools create \\\n            --tag ghcr.io/grindsa/acme2certifier:${{ env.OLD_TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }} \\\n            grindsa/acme2certifier:${{ env.OLD_TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}\n\n      - name: Delete image from registry\n        run: |\n          docker images\n      #    docker rmi $(docker images grindsa/acme2certifier -q) --force\n\n      - name: Checkout code for latest release\n        uses: actions/checkout@v6\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: all\n\n      - uses: docker/setup-buildx-action@v3\n        with:\n          version: latest\n          buildkitd-flags: --debug\n\n      - name: Build with latest tag\n        uses: docker/build-push-action@v5\n        if: ${{ env.BUILD_NAME == 'apache2-wsgi'}}\n        with:\n          push: true\n          tags: grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }}, grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}, grindsa/acme2certifier:latest\n          file: examples/Docker/${{ matrix.websrv }}/${{ matrix.dbhandler }}/Dockerfile\n          platforms: linux/arm64, linux/amd64\n\n      - name: Build without latest tag\n        uses: docker/build-push-action@v5\n        if: ${{ env.BUILD_NAME != 'apache2-wsgi'}}\n        with:\n          push: true\n          tags: grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }}, grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}\n          file: examples/Docker/${{ matrix.websrv }}/${{ matrix.dbhandler }}/Dockerfile\n          platforms: linux/arm64, linux/amd64\n\n      - name: Push image with latest tag to GHCR\n        if: ${{ env.BUILD_NAME == 'apache2-wsgi'}}\n        run: |\n          docker buildx imagetools create \\\n            --tag ghcr.io/grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }} \\\n            --tag ghcr.io/grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }} \\\n            --tag ghcr.io/grindsa/acme2certifier:latest \\\n            grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}\n\n      - name: Push image without latest tag to GHCR\n        if: ${{ env.BUILD_NAME != 'apache2-wsgi'}}\n        run: |\n          docker buildx imagetools create \\\n            --tag ghcr.io/grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }} \\\n            --tag ghcr.io/grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }} \\\n            grindsa/acme2certifier:${{ env.TAG_NAME }}-${{ matrix.websrv }}-${{ matrix.dbhandler }}\n\n  amd64_pull_and_test:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    needs: build_and_upload_images_to_hub\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"Get current version\"\n        uses: oprypin/find-latest-tag@v1\n        with:\n          repository: ${{ github.repository }}  # The repository to scan.\n          releases-only: true  # We know that all relevant tags have a GitHub release for them.\n        id: acme2certifier_ver  # The step ID to refer to later.\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Parse software repository secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SWREPO_CFG }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n\n      - name: \"Display version information\"\n        run: |\n          echo \"Repo is at version $ACME2CERTIFIER_VERSION\"\n          echo \"Latest tag is $TAG_NAME\"\n        env:\n          ACME2CERTIFIER_VERSION: ${{ steps.acme2certifier_ver.outputs.tag }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          sudo mkdir -p acme-sh\n          sudo mkdir -p certbot\n          sudo mkdir -p lego\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo cp .github/acme2certifier.pem examples/Docker/data/acme2certifier.pem\n          sudo cp .github/django_settings.py examples/Docker/data/settings.py\n          sudo cp .github/acme2certifier_cert.pem examples/Docker/data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem examples/Docker/data/acme2certifier_key.pem\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ env.DOCKERHUB_USER }}\n          password: ${{ env.DOCKERHUB_TOKEN }}\n\n      - name: \"Pull images from dockerhub and setup container\"\n        run: |\n          docker run -d -p 80:80 --rm -id --network acme --name=acme-srv -v \"$(pwd)/examples/Docker/data\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:$TAG_NAME-$WEB_SRV-$DB_HANDLER\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n\n      - name: \"[ WAIT ] Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test if http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Enroll via acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --alpn --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          ls -la *.pem\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Revoke via acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --revoke -d acme-sh.acme --standalone --debug 3 --output-insecure\n\n      - name: \"Register certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"Enroll certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n\n      - name: \"Revoke via certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv  -d certbot.acme --cert-name certbot\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Revoke via lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme revoke\n\n      - name: \"Install syft\"\n        run: |\n          sudo curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin\n\n      - name: Parse GitHub secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: Parse software repository secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.SWREPO_CFG }}\n\n      - name: \"Retrieve SBOM repo\"\n        run: |\n          git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n        env:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n\n      - name: \"Generate SBOMs for acme2certifier-${{ matrix.websrv }}-${{ matrix.dbhandler }}\"\n        run: |\n          mkdir -p /tmp/sbom/sbom/acme2certifier\n          syft grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }} > /tmp/sbom/sbom/acme2certifier/acme2certifier-${{ matrix.websrv }}-${{ matrix.dbhandler }}_sbom.txt\n          syft grindsa/acme2certifier:${{ matrix.websrv }}-${{ matrix.dbhandler }} -o json > /tmp/sbom/sbom/acme2certifier/acme2certifier-${{ matrix.websrv }}-${{ matrix.dbhandler }}_sbom.json\n          ls -la /tmp/sbom/sbom/acme2certifier\n\n      - name: \"Upload Changes\"\n        continue-on-error: true\n        run: |\n          cd /tmp/sbom\n          git config --global user.email \"grindelsack@gmail.com\"\n          git config --global user.name \"SBOM Generator\"\n          git add sbom/acme2certifier/\n          git commit -a -m \"SBOM update\"\n          git push\n\n      - name: \"Delete images from local repository\"\n        run: |\n          docker stop acme-srv\n          docker rmi $(docker images grindsa/acme2certifier -q) --no-prune --force\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          cd examples/Docker\n          docker logs acme-srv > ${{ github.workspace }}/artifact/acme-srv.log 2>&1\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme-srv.log data # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: amd64_pull_and_test-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  arm64_pull_and_test:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    needs: build_and_upload_images_to_hub\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"Get current version\"\n        uses: oprypin/find-latest-tag@v1\n        with:\n          repository: ${{ github.repository }}  # The repository to scan.\n          releases-only: true  # We know that all relevant tags have a GitHub release for them.\n        id: acme2certifier_ver  # The step ID to refer to later.\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Parse AWS secrets from JSON\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.AWS_CFG }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n          echo UUID=$(uuidgen) >> $GITHUB_ENV\n\n      - name: \"Display version information\"\n        run: |\n          echo \"Repo is at version $ACME2CERTIFIER_VERSION\"\n          echo \"UUID $UUID\"\n        env:\n          ACME2CERTIFIER_VERSION: ${{ steps.acme2certifier_ver.outputs.tag }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Prepare ssh environment in ramdisk\"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ env.AWS_SSH_KEY }}\n          KNOWN_HOSTS: ${{ env.AWS_SSH_KNOWN_HOSTS }}\n\n      - name: \"Create working directory on remote host\"\n        run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts  mkdir -p /tmp/a2c/$UUID\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Prepare and data package\"\n        run: |\n          sudo mkdir -p /tmp/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /tmp/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /tmp/data/acme_srv.cfg\n          sudo cp .github/acme2certifier.pem /tmp/data/acme2certifier.pem\n          sudo cp .github/django_settings.py /tmp/data/settings.py\n          sudo cp .github/acme2certifier_cert.pem /tmp/data/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem /tmp/data/acme2certifier_key.pem\n\n      - name: \"Copy data package to remote host\"\n        run: sudo scp -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts -r /tmp/data $SSH_USER@$SSH_HOST:/tmp/a2c/$UUID/\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          UUID: ${{ env.UUID }}\n\n      - run: echo \"Image name - grindsa/acme2certifier:$TAG_NAME-$WEB_SRV-$DB_HANDLER\"\n        env:\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n\n      - name: \"Pull images from dockerhub and setup container\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker network create $UUID\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -d --rm -id --network $UUID --name=acme-srv-$UUID -v \"/tmp/a2c/$UUID/data\":/var/www/acme2certifier/volume/ grindsa/acme2certifier:$TAG_NAME-$WEB_SRV-$DB_HANDLER\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test http://acme-srv/directory internally\"\n        run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --network $UUID curlimages/curl -f http://acme-srv-$UUID/directory\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Test if https://acme-srv/directory internally\"\n        run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --network $UUID curlimages/curl --insecure -f https://acme-srv-$UUID/directory\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"acme.sh enroll\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/acme-sh\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run --rm -id -v /tmp/a2c/$UUID/acme-sh:/acme.sh --network $UUID --name=acme-sh-$UUID neilpang/acme.sh:latest daemon\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker exec -i acme-sh-$UUID acme.sh --server http://acme-srv-$UUID  --accountemail 'acme-sh@example.com' --issue -d acme-sh-$UUID --standalone --debug 3 --output-insecure --force\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"acme.sh revoke\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker exec -i acme-sh-$UUID acme.sh --server http://acme-srv-$UUID --revoke -d acme-sh-$UUID --standalone --debug 3 --output-insecure\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Certbot enroll\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/certbot\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv-$UUID --no-eff-email\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv-$UUID --standalone --preferred-challenges http -d certbot-$UUID --cert-name certbot-$UUID\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Certbot revoke\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i --rm --name certbot-$UUID --network $UUID -v /tmp/a2c/$UUID/certbot:/etc/letsencrypt/ certbot/certbot revoke --delete-after-revoke --server http://acme-srv-$UUID -d certbot-$UUID --cert-name certbot-$UUID\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Lego enroll\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"mkdir -p /tmp/a2c/$UUID/lego\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i -v /tmp/a2c/$UUID/lego:/.lego/ --rm --name lego-$UUID --network $UUID goacme/lego --tls-skip-verify -s https://acme-srv-$UUID/directory -a --email lego@example.com -d lego-$UUID --http run\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Lego revoke\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker run -i -v /tmp/a2c/$UUID/lego:/.lego/ --rm --name lego-$UUID  --network $UUID goacme/lego --tls-skip-verify -s https://acme-srv-$UUID -a --email \"lego@example.com\" -d lego-$UUID revoke\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          UUID: ${{ env.UUID }}\n\n      - name: \"Cleanup on remote host\"\n        run: |\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker stop acme-sh-$UUID\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker stop acme-srv-$UUID\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker network rm $UUID\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"docker image rm grindsa/acme2certifier:$TAG_NAME-$WEB_SRV-$DB_HANDLER\"\n          sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -o UserKnownHostsFile=/tmp/rd/known_hosts \"sudo rm -rf /tmp/a2c/$UUID\"\n        env:\n          SSH_USER: ${{ env.AWS_SSH_USER }}\n          SSH_HOST: ${{ env.AWS_SSH_HOST }}\n          WEB_SRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          UUID: ${{ env.UUID }}\n\n  instance_stop:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    needs: arm64_pull_and_test\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v6\n\n    - name: Parse AWS secrets from JSON\n      uses: ./.github/actions/parse-json-secret\n      with:\n        json_secret: ${{ secrets.AWS_CFG }}\n\n    - name: \"install awccli\"\n      run: |\n        sudo apt-get update\n        pip3 install awscli --upgrade --user\n        pip3 install boto3 --upgrade --user\n        export PATH=$PATH:$HOME/.local/bin\n\n    - name: \"configure awccli\"\n      run: |\n        aws --version\n        aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID\n        aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY\n        aws configure set default.region $AWS_REGION\n      env:\n        AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}\n        AWS_REGION: ${{ env.AWS_REGION }}\n\n    - name: \"stop instance\"\n      run: |\n        wget https://raw.githubusercontent.com/grindsa/aws_ec2_mgr/main/aws_ec_mgr.py\n        chmod a+rx ./aws_ec_mgr.py\n        python3 ./aws_ec_mgr.py -a stop -r $AWS_REGION -i $AWS_INSTANCE_ID\n        python3 ./aws_ec_mgr.py -a state -r $AWS_REGION -i $AWS_INSTANCE_ID\n      env:\n        AWS_REGION: ${{ env.AWS_REGION }}\n        AWS_INSTANCE_ID: ${{ env.AWS_INSTANCE_ID }}\n"
  },
  {
    "path": ".github/workflows/deployment-upgrade.yml",
    "content": "name: Deployment Tests - Upgrades\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n        django_db: ['mariadb', 'psql', 'sqlite3']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Get current version\"\n        uses: oprypin/find-latest-tag@v1\n        with:\n          repository: ${{ github.repository }}  # The repository to scan.\n          releases-only: true  # We know that all relevant tags have a GitHub release for them.\n        id: acme2certifier_ver  # The step ID to refer to later.\n\n      - name: \"Retrieve version from version.py\"\n        run: |\n          echo APP_NAME=$(echo ${{ github.repository }} | awk -F / '{print $2}') >> $GITHUB_ENV\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n          echo BUILD_NAME=${{ matrix.websrv }}-${{ matrix.dbhandler }} >> $GITHUB_ENV\n\n      - name: \"Retrieve 2nd last release tag\"\n        run: |\n          VERSION=$(echo ${{ env.TAG_NAME }} | awk -F. '{print $2}')\n          PRE_VERSION=$(($VERSION - 1))\n          echo $PRE_VERSION\n          for row in $(curl https://api.github.com/repos/grindsa/acme2certifier/tags | jq .[].name);\n            do\n                if [[ $row =~ $PRE_VERSION ]]; then\n                  echo OLD_TAG_NAME=$(echo $row | sed s/\\\"//g) >> $GITHUB_ENV\n                  echo $row\n                  break\n                fi\n            done\n\n      - run: echo \"Repo is at version ${{ steps.acme2certifier_ver.outputs.tag }}\"\n      - run: echo \"APP tag is ${{ env.APP_NAME }}\"\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n      - run: echo \"Old tag is ${{ env.OLD_TAG_NAME }}\"\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          DJANGO_DB: ${{ matrix.django_db }}\n\n      - name: \"Configure acme2certifier\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          echo \"\" >> examples/Docker/data/acme_srv.cfg\n          echo \"handler_file: examples/ca_handler/openssl_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Build acme2certifier image at v0.19.3\"\n        run: |\n          git clone https://github.com/grindsa/acme2certifier.git /tmp/acme2certifier\n          cd /tmp/acme2certifier\n          git checkout 0.19.3\n          docker build -f examples/Docker/$WEBSRV/$DB_HANDLER/Dockerfile -t grindsa/acme2certifier:$VERSION-$WEBSRV-$DB_HANDLER .\n        env:\n          VERSION: \"0.19.3\"\n          WEBSRV: ${{ matrix.websrv }}\n          DB_HANDLER: ${{ matrix.dbhandler }}\n\n      - name: \"Spin-up a2c 0.19.3 instance\"\n        uses: ./.github/actions/container_run\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          VERSION: \"0.19.3\"\n\n      - name: \"Enroll and renew certificates\"\n        uses: ./.github/actions/wf_specific/upgrade/enroll\n        with:\n          RENEWAL: true\n\n      - name : \"Stop and remove a2c instance\"\n        run: docker stop acme-srv\n\n      - name: \"Spin-up latest a2c instance\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Renew certificates\"\n        uses: ./.github/actions/wf_specific/upgrade/renew\n        with:\n          CLEANUP: true\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Stop a2c instance\"\n        uses: ./.github/actions/container_down\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Cleanup environment\"\n        uses: ./.github/actions/wf_specific/upgrade/cleanup\n        with:\n          DJANGO_DB: ${{ matrix.django_db }}\n\n      - name: \"Spin-up a2c ${{ env.OLD_TAG_NAME }} instance\"\n        uses: ./.github/actions/container_run\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          VERSION: ${{ env.OLD_TAG_NAME }}\n\n      - name: \"Enroll and renew certificates\"\n        uses: ./.github/actions/wf_specific/upgrade/enroll\n        with:\n          RENEWAL: true\n\n      - name : \"Stop and remove a2c instance\"\n        run: docker stop acme-srv\n\n      - name: \"Spin-up latest a2c instance\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Renew certificates\"\n        uses: ./.github/actions/wf_specific/upgrade/renew\n        with:\n          CLEANUP: true\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Stop a2c instance\"\n        uses: ./.github/actions/container_down\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Cleanup environment\"\n        uses: ./.github/actions/wf_specific/upgrade/cleanup\n        with:\n          DJANGO_DB: ${{ matrix.django_db }}\n\n      - name: \"Spin-up a2c ${{ env.TAG_NAME }} instance\"\n        if: github.ref != 'refs/heads/master'\n        uses: ./.github/actions/container_run\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          VERSION: ${{ env.TAG_NAME }}\n\n      - name: \"Enroll and renew certificates\"\n        if: github.ref != 'refs/heads/master'\n        uses: ./.github/actions/wf_specific/upgrade/enroll\n        with:\n          RENEWAL: true\n\n      - name : \"Stop and remove a2c instance\"\n        if: github.ref != 'refs/heads/master'\n        run: docker stop acme-srv\n\n      - name: \"Spin-up latest a2c instance\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Renew certificates\"\n        if: github.ref != 'refs/heads/master'\n        uses: ./.github/actions/wf_specific/upgrade/renew\n        with:\n          CLEANUP: true\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Stop a2c instance\"\n        uses: ./.github/actions/container_down\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Cleanup environment\"\n        uses: ./.github/actions/wf_specific/upgrade/cleanup\n        with:\n          DJANGO_DB: ${{ matrix.django_db }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker logs acme-srv > ${{ github.workspace }}/artifact/acme-srv.log\n          # docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz acme-srv.log data\n          # sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}-${{ matrix.django_db }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) upgrade for wsgi and django handlers\n  # ---------------------------------------------------------\n  test-rpm-wsgi-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup environment for alma installation\"\n        run: |\n          docker network create acme\n          sudo mkdir -p data/volume\n          sudo mkdir -p data/acme2certifier\n          sudo mkdir -p data/nginx/conf.d\n          sudo chmod -R 777 data\n          sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier-0.23.2-1.0.noarch.rpm\n          sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n          sudo cp examples/nginx/nginx_acme_srv.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv.conf\n          sudo cp examples/nginx/nginx_acme_srv_ssl.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv_ssl.conf\n\n      - name: \"Retrieve rpms from SBOM repo\"\n        run: |\n          git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n          cp /tmp/sbom/rpm-repo/RPMs/rhel9/*.rpm  data\n\n      - name: \"Prepare acme_srv.cfg with xca_ca_handler\"\n        run: |\n          sudo mkdir acme-sh\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Almalinux instance\"\n        run: |\n          cat examples/Docker/almalinux-systemd/Dockerfile | docker build -t almalinux-systemd -f - . --no-cache\n          docker run -d -id --privileged --network acme -p 22280:80 --name=acme-srv -v \"$(pwd)/data\":/tmp/acme2certifier almalinux-systemd\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/rpm_tester.sh\n          sudo docker cp data/nginx acme-srv:/etc\n          sudo docker cp data/volume/ acme-srv:/opt/acme2certifier/\n          docker exec acme-srv chmod -R 777 /opt/acme2certifier/volume\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Update acme2certifier\"\n        run: |\n          docker cp /tmp/acme2certifier-$RUN_ID.noarch.rpm acme-srv:/tmp\n          docker exec acme-srv yum -y localinstall /tmp/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec -w /opt/acme2certifier acme-srv python3 tools/db_update.py\n          docker restart acme-srv\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /opt/acme2certifier/examples/db_handler/wsgi_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /opt/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: rpm_wsgi_upgrade_nginx.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-rpm-mariadb-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: update version number in spec file and path in nginx ssl config\n        run: |\n          sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" examples/install_scripts/rpm/acme2certifier.spec\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\" examples/nginx/nginx_acme_srv_ssl.conf\n          git config --global user.email \"grindelsack@gmail.com\"\n          git config --global user.name \"rpm update\"\n          git add examples/nginx\n          git commit -a -m \"rpm update\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup environment for alma installation\"\n        run: |\n          sudo mkdir acme-sh\n          docker network create acme\n          sudo mkdir -p data/volume\n          sudo mkdir -p data/acme2certifier\n          sudo mkdir -p data/nginx/conf.d\n          sudo chmod -R 777 data\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier-0.23.2-1.0.noarch.rpm\n          sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          sudo cp examples/Docker/almalinux-systemd/django_tester.sh data\n          sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n          sudo cp .github/django_settings_mariadb.py data/acme2certifier/settings.py\n          # sudo sed -i \"s/\\/var\\/www\\//\\/opt\\//g\" data/acme2certifier/settings.py\n          sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/acme2certifier/settings.py\n          sudo cp examples/nginx/nginx_acme_srv.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv.conf\n          sudo cp examples/nginx/nginx_acme_srv_ssl.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv_ssl.conf\n\n      - name: \"Instanciate mariadb\"\n        uses: ./.github/actions/mariadb_prep\n\n      - name: \"Retrieve rpms from SBOM repo\"\n        run: |\n          git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n          cp /tmp/sbom/rpm-repo/RPMs/rhel9/*.rpm  data\n\n      - name: \"Configure acme2certifier\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Almalinux instance\"\n        run: |\n          cat examples/Docker/almalinux-systemd/Dockerfile | docker build -t almalinux-systemd -f - . --no-cache\n          docker run -d -id --privileged --network acme -p 22280:80 --name=acme-srv -v \"$(pwd)/data\":/tmp/acme2certifier almalinux-systemd\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/django_tester.sh\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Update acme2certifier\"\n        run: |\n          docker cp /tmp/acme2certifier-$RUN_ID.noarch.rpm acme-srv:/tmp\n          docker exec acme-srv yum -y localinstall /tmp/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec -w /opt/acme2certifier acme-srv python3 tools/django_update.py\n          docker restart acme-srv\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /opt/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /opt/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-mariadb-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-rpm-sqlite-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: update version number in spec file and path in nginx ssl config\n        run: |\n          sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" examples/install_scripts/rpm/acme2certifier.spec\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\" examples/nginx/nginx_acme_srv_ssl.conf\n          git config --global user.email \"grindelsack@gmail.com\"\n          git config --global user.name \"rpm update\"\n          git add examples/nginx\n          git commit -a -m \"rpm update\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup environment for alma installation\"\n        run: |\n          sudo mkdir acme-sh\n          docker network create acme\n          sudo mkdir -p data/volume\n          sudo mkdir -p data/acme2certifier\n          sudo mkdir -p data/nginx/conf.d\n          sudo chmod -R 777 data\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier-0.23.2-1.0.noarch.rpm\n          sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          sudo cp examples/Docker/almalinux-systemd/django_tester.sh data\n          sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n          sudo cp .github/django_settings.py data/acme2certifier/settings.py\n          sudo sed -i \"s/\\/var\\/www\\//\\/opt\\//g\" data/acme2certifier/settings.py\n          sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/acme2certifier/settings.py\n          sudo cp examples/nginx/nginx_acme_srv.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv.conf\n          sudo cp examples/nginx/nginx_acme_srv_ssl.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv_ssl.conf\n\n      - name: \"Retrieve rpms from SBOM repo\"\n        run: |\n          git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n          cp /tmp/sbom/rpm-repo/RPMs/rhel9/*.rpm  data\n\n      - name: \"Configure acme2certifier\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Almalinux instance\"\n        run: |\n          cat examples/Docker/almalinux-systemd/Dockerfile | docker build -t almalinux-systemd -f - . --no-cache\n          docker run -d -id --privileged --network acme -p 22280:80 --name=acme-srv -v \"$(pwd)/data\":/tmp/acme2certifier almalinux-systemd\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/django_tester.sh\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Update acme2certifier\"\n        run: |\n          docker cp /tmp/acme2certifier-$RUN_ID.noarch.rpm acme-srv:/tmp\n          docker exec acme-srv yum -y localinstall /tmp/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec -w /opt/acme2certifier acme-srv python3 tools/django_update.py\n          docker restart acme-srv\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /opt/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /opt/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-sqlite-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-rpm-psql-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: update version number in spec file and path in nginx ssl config\n        run: |\n          sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" examples/install_scripts/rpm/acme2certifier.spec\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\" examples/nginx/nginx_acme_srv_ssl.conf\n          git config --global user.email \"grindelsack@gmail.com\"\n          git config --global user.name \"rpm update\"\n          git add examples/nginx\n          git commit -a -m \"rpm update\"\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: /tmp/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup environment for alma installation\"\n        run: |\n          sudo mkdir acme-sh\n          docker network create acme\n          sudo mkdir -p data/volume\n          sudo mkdir -p data/acme2certifier\n          sudo mkdir -p data/nginx/conf.d\n          sudo chmod -R 777 data\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier-0.23.2-1.0.noarch.rpm\n          sudo cp examples/Docker/almalinux-systemd/rpm_tester.sh data\n          sudo cp examples/Docker/almalinux-systemd/django_tester.sh data\n          sudo cp .github/acme2certifier_cert.pem data/nginx/acme2certifier_cert.pem\n          sudo cp .github/acme2certifier_key.pem data/nginx/acme2certifier_key.pem\n          sudo cp .github/django_settings_psql.py data/acme2certifier/settings.py\n          # sudo sed -i \"s/\\/var\\/www\\//\\/opt\\//g\" data/acme2certifier/settings.py\n          sudo sed -i \"s/USE_I18N = True/USE_I18N = False/g\" data/acme2certifier/settings.py\n          sudo cp examples/nginx/nginx_acme_srv.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv.conf\n          sudo cp examples/nginx/nginx_acme_srv_ssl.conf data/nginx/conf.d\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\"  data/nginx/conf.d/nginx_acme_srv_ssl.conf\n\n      - name: \"Instanciate postgres\"\n        uses: ./.github/actions/psql_prep\n\n      - name: \"Retrieve rpms from SBOM repo\"\n        run: |\n          git clone https://$GH_USER:$GH_SBOM_REPO_TOKEN@github.com/$GH_USER/sbom /tmp/sbom\n          cp /tmp/sbom/rpm-repo/RPMs/rhel9/*.rpm  data\n\n      - name: \"Configure acme2certifier\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Almalinux instance\"\n        run: |\n          cat examples/Docker/almalinux-systemd/Dockerfile | docker build -t almalinux-systemd -f - . --no-cache\n          docker run -d -id --privileged --network acme -p 22280:80 --name=acme-srv -v \"$(pwd)/data\":/tmp/acme2certifier almalinux-systemd\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/django_tester.sh\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n\n      - name: \"Update acme2certifier\"\n        run: |\n          docker cp /tmp/acme2certifier-$RUN_ID.noarch.rpm acme-srv:/tmp\n          docker exec acme-srv yum -y localinstall /tmp/acme2certifier-$RUN_ID.noarch.rpm\n          docker exec -w /opt/acme2certifier acme-srv python3 tools/django_update.py\n          docker restart acme-srv\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /opt/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /opt/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-psql-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB upgrade for wsgi and django\n  # ---------------------------------------------------------\n  deb_wsgi-upgrade:\n    needs: guard\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          mkdir acme-sh\n          mkdir certbot\n          mkdir -p data/volume\n\n      - name: \"Download a2c 0.23 deb package\"\n        run: |\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier_0.23.2-1_all.deb\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: List files\n        run: ls -la data/\n\n      - name: \"Instanciate Ubuntu 22.04\"\n        run: |\n          docker run -d --name acme-srv --network acme --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v \"$(pwd)/data\":/tmp/acme2certifier jrei/systemd-ubuntu:22.04\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Install a2c\"\n        run: |\n          docker exec acme-srv apt-get update\n          docker exec acme-srv apt-get -y upgrade\n          docker exec acme-srv apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n          docker exec acme-srv ls -la /tmp/acme2certifier/\n          docker exec acme-srv apt-get install -y /tmp/acme2certifier/acme2certifier_0.23.2-1_all.deb\n\n      - name: \"Configure a2c\"\n        run: |\n          sudo cp .github/acme2certifier.pem data/volume/acme2certifier.pem\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n          docker exec acme-srv a2enmod ssl\n          docker exec acme-srv a2ensite acme2certifier\n          docker exec acme-srv a2ensite acme2certifier_ssl\n          docker exec acme-srv rm /etc/apache2/sites-enabled/000-default.conf\n          docker exec acme-srv mkdir -p /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme2certifier.pem /var/www/acme2certifier/volume/\n          docker exec acme-srv systemctl start apache2\n\n      - name: \"Setup xca-handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/$XCA_DB_NAME\n\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec acme-srv cp /tmp/acme2certifier/volume/$XCA_DB_NAME /var/www/acme2certifier/volume/\n          docker exec acme-srv chown -R www-data.www-data /var/www/acme2certifier/volume\n          docker exec acme-srv systemctl restart apache2\n          docker exec acme-srv systemctl status apache2\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Upgrade a2c\"\n        run: |\n          docker exec acme-srv apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' /tmp/acme2certifier/acme2certifier-$RUN_ID-1_all.deb\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/db_update.py\n          docker exec acme-srv systemctl restart apache2\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test http://acme-srv/directory is accessible after upgrade\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of wsgi_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /var/www/acme2certifier/examples/db_handler/wsgi_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /var/www/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-sh certbot acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-wsgi-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-deb_sqlite-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          mkdir acme-sh\n          mkdir certbot\n          mkdir -p data/volume\n\n      - name: \"Download a2c 0.23 deb package\"\n        run: |\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier_0.23.2-1_all.deb\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: List files\n        run: ls -la data/\n\n      - name: \"Instanciate Ubuntu 22.04\"\n        run: |\n          docker run -d --name acme-srv --network acme --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v \"$(pwd)/data\":/tmp/acme2certifier jrei/systemd-ubuntu:22.04\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Install a2c\"\n        run: |\n          docker exec acme-srv apt-get update\n          docker exec acme-srv apt-get -y upgrade\n          docker exec acme-srv apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n          docker exec acme-srv ls -la /tmp/acme2certifier/\n          docker exec acme-srv apt-get install -y /tmp/acme2certifier/acme2certifier_0.23.2-1_all.deb\n\n      - name: \"Configure a2c\"\n        run: |\n          sudo cp .github/acme2certifier.pem data/volume/acme2certifier.pem\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-available/acme2certifier.conf\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n          docker exec acme-srv a2enmod ssl\n          docker exec acme-srv a2ensite acme2certifier\n          docker exec acme-srv a2ensite acme2certifier_ssl\n          docker exec acme-srv rm /etc/apache2/sites-enabled/000-default.conf\n          docker exec acme-srv mkdir -p /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme2certifier.pem /var/www/acme2certifier/volume/\n          docker exec acme-srv systemctl start apache2\n\n      - name: \"Setup xca-handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/$XCA_DB_NAME\n          sudo cp .github/django_settings.py data/volume/settings.py\n          docker exec acme-srv bash -c \"cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\"\n          docker exec acme-srv cp -r /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec acme-srv cp /tmp/acme2certifier/volume/$XCA_DB_NAME /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/settings.py /var/www/acme2certifier/acme2certifier/\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv chown -R www-data.www-data /var/www/acme2certifier/volume\n          docker exec acme-srv systemctl restart apache2\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Upgrade a2c\"\n        run: |\n          docker exec acme-srv apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' /tmp/acme2certifier/acme2certifier-$RUN_ID-1_all.deb\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv systemctl restart apache2\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test http://acme-srv/directory is accessible after upgrade\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /var/www/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /var/www/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-sh certbot acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-sqlite-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-deb-mariadb-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          mkdir acme-sh\n          mkdir certbot\n          mkdir -p data/volume\n\n      - name: \"Install mariadb\"\n        working-directory: examples/Docker/\n        run: |\n          # docker run --name mariadbsrv --network acme -v $PWD/data/mysql:/var/lib/mysql -e MARIADB_ROOT_PASSWORD=foobar -d mariadb\n          docker run --name mariadbsrv --network acme -e MARIADB_ROOT_PASSWORD=foobar -d mariadb\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Configure mariadb\"\n        working-directory: examples/Docker/\n        run: |\n          docker exec mariadbsrv mariadb -u root --password=foobar -e\"CREATE DATABASE acme2certifier CHARACTER SET UTF8;\"\n          docker exec mariadbsrv mariadb -u root --password=foobar -e\"GRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY '1mmSvDFl';\"\n          docker exec mariadbsrv mariadb -u root --password=foobar -e\"FLUSH PRIVILEGES;\"\n\n      - name: \"Download a2c 0.23 deb package\"\n        run: |\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier_0.23.2-1_all.deb\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: List files\n        run: ls -la data/\n\n      - name: \"Instanciate Ubuntu 22.04\"\n        run: |\n          docker run -d --name acme-srv --network acme --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v \"$(pwd)/data\":/tmp/acme2certifier jrei/systemd-ubuntu:22.04\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Install a2c\"\n        run: |\n          docker exec acme-srv apt-get update\n          docker exec acme-srv apt-get -y upgrade\n          docker exec acme-srv apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n          docker exec acme-srv ls -la /tmp/acme2certifier/\n          docker exec acme-srv apt-get install -y /tmp/acme2certifier/acme2certifier_0.23.2-1_all.deb\n\n      - name: \"Configure a2c\"\n        run: |\n          sudo cp .github/acme2certifier.pem data/volume/acme2certifier.pem\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-available/acme2certifier.conf\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n          docker exec acme-srv a2enmod ssl\n          docker exec acme-srv a2ensite acme2certifier\n          docker exec acme-srv a2ensite acme2certifier_ssl\n          docker exec acme-srv rm /etc/apache2/sites-enabled/000-default.conf\n          docker exec acme-srv mkdir -p /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme2certifier.pem /var/www/acme2certifier/volume/\n          docker exec acme-srv systemctl start apache2\n\n      - name: \"Setup xca-handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/$XCA_DB_NAME\n\n          sudo cp .github/django_settings_mariadb.py data/volume/settings.py\n          docker exec acme-srv bash -c \"cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\"\n          docker exec acme-srv cp -r /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec acme-srv cp /tmp/acme2certifier/volume/$XCA_DB_NAME /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/settings.py /var/www/acme2certifier/acme2certifier/\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv chown -R www-data.www-data /var/www/acme2certifier/volume\n          docker exec acme-srv systemctl restart apache2\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Upgrade a2c\"\n        run: |\n          docker exec acme-srv apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' /tmp/acme2certifier/acme2certifier-$RUN_ID-1_all.deb\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv systemctl restart apache2\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test http://acme-srv/directory is accessible after upgrade\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /var/www/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /var/www/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          docker exec mariadbsrv mysqldump -u root --password=foobar acme2certifier > /tmp/acme2certifier.sql\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp /tmp/acme2certifier.sql ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-sh certbot acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-mariadb-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-deb-psql-upgrade:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    needs: guard\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare environment\"\n        run: |\n          docker network create acme\n          mkdir acme-sh\n          mkdir certbot\n          mkdir -p data/volume\n\n      - name: \"Prepare Postgres environment\"\n        run: |\n          sudo mkdir -p /tmp/data/pgsql\n          sudo cp .github/a2c.psql /tmp/data/pgsql/a2c.psql\n          sudo cp .github/pgpass /tmp//data/pgsql/pgpass\n          sudo chmod 600 /tmp/data/pgsql/pgpass\n\n      - name: \"Install postgres\"\n        working-directory: /tmp\n        run: |\n          docker run --name postgresdbsrv --network acme -e POSTGRES_PASSWORD=foobar -d postgres\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Configure postgres\"\n        working-directory: /tmp\n        run: |\n          docker run -v \"$(pwd)/data/pgsql/a2c.psql\":/tmp/a2c.psql -v \"$(pwd)/data/pgsql/pgpass:/root/.pgpass\" --rm --network acme postgres psql -U postgres -h postgresdbsrv -f /tmp/a2c.psql\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Download a2c 0.23 deb package\"\n        run: |\n          wget -P data/ https://github.com/grindsa/acme2certifier/releases/download/0.23.2/acme2certifier_0.23.2-1_all.deb\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.TAG_NAME }}\"\n\n      - name: \"Generate keys and certificates\"\n        uses: ./.github/actions/cert_gen\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: List files\n        run: ls -la data/\n\n      - name: \"Instanciate Ubuntu 22.04\"\n        run: |\n          docker run -d --name acme-srv --network acme --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host -v \"$(pwd)/data\":/tmp/acme2certifier jrei/systemd-ubuntu:22.04\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Install a2c\"\n        run: |\n          docker exec acme-srv apt-get update\n          docker exec acme-srv apt-get -y upgrade\n          docker exec acme-srv apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n          docker exec acme-srv ls -la /tmp/acme2certifier/\n          docker exec acme-srv apt-get install -y /tmp/acme2certifier/acme2certifier_0.23.2-1_all.deb\n\n      - name: \"Configure a2c\"\n        run: |\n          sudo cp .github/acme2certifier.pem data/volume/acme2certifier.pem\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-available/acme2certifier.conf\n          docker exec acme-srv cp /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n          docker exec acme-srv a2enmod ssl\n          docker exec acme-srv a2ensite acme2certifier\n          docker exec acme-srv a2ensite acme2certifier_ssl\n          docker exec acme-srv rm /etc/apache2/sites-enabled/000-default.conf\n          docker exec acme-srv mkdir -p /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme2certifier.pem /var/www/acme2certifier/volume/\n          docker exec acme-srv systemctl start apache2\n\n      - name: \"Setup xca-handler\"\n        run: |\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/$XCA_DB_NAME\n\n          sudo cp .github/django_settings_psql.py data/volume/settings.py\n          docker exec acme-srv bash -c \"cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\"\n          docker exec acme-srv cp -r /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n          docker exec acme-srv cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n          docker exec acme-srv cp /tmp/acme2certifier/volume/$XCA_DB_NAME /var/www/acme2certifier/volume/\n          docker exec acme-srv cp /tmp/acme2certifier/volume/settings.py /var/www/acme2certifier/acme2certifier/\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv chown -R www-data.www-data /var/www/acme2certifier/volume\n          docker exec acme-srv systemctl restart apache2\n\n      - name: \"Test enrollment\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http run\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot\n\n      - name: \"Test renewal\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n\n      - name: \"Upgrade a2c\"\n        run: |\n          docker exec acme-srv apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' /tmp/acme2certifier/acme2certifier-$RUN_ID-1_all.deb\n          docker exec -w /var/www/acme2certifier acme-srv python3 tools/django_update.py\n          docker exec acme-srv systemctl restart apache2\n        env:\n          RUN_ID: ${{ inputs.run_id }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Test http://acme-srv/directory is accessible after upgrade\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Get hashes of django_handler.py and db_handler.py\"\n        run: |\n          echo HASH1=$(docker exec acme-srv sha256sum /var/www/acme2certifier/examples/db_handler/django_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n          echo HASH2=$(docker exec acme-srv sha256sum /var/www/acme2certifier/acme_srv/db_handler.py | awk -F ' '  '{ print $1 }') >> $GITHUB_ENV\n\n      - run: echo \"Hash1 is ${{ env.HASH1 }}\"\n      - run: echo \"Hash2 is ${{ env.HASH2 }}\"\n\n      - name: Compare hashes\n        if: env.HASH1 != env.HASH2\n        run: |\n          exit 1\n\n      - name: \"Test renewal again\"\n        run: |\n          docker run -i -v \"$(pwd)/lego\":/.lego/ --network acme --rm --name lego goacme/lego -s https://acme-srv.acme -a --email \"lego@example.com\"  -d lego.acme --tls-skip-verify --http renew --days 364 --no-random-sleep\n          docker run -i --rm --name certbot --network acme -v \"$(pwd)/certbot\":/etc/letsencrypt/ certbot/certbot certonly --server https://acme-srv.acme --standalone --preferred-challenges http --no-verify-ssl --agree-tos -m 'certbot@example.com' -d certbot.acme --cert-name certbot --force-renewal\n          sudo rm -rf $(pwd)/lego\n          sudo rm -rf $(pwd)/certbot\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-sh certbot acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-psql-upgrade.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deployment-wsgi.yml",
    "content": "name: Feature Tests - custom db-file\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo \"\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"[DBhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"dbfile: volume/a2c.db\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"[Directory]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"url_prefix: /foo\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: wsgi\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs/\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo \"[DBhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"dbfile: volume/a2c.db\" >> data/volume/acme_srv.cfg\n          sudo echo \"[Directory]\" >> data/volume/acme_srv.cfg\n          sudo echo \"url_prefix: /foo\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs/\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo \"[DBhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"dbfile: volume/a2c.db\" >> data/volume/acme_srv.cfg\n          sudo echo \"[Directory]\" >> data/volume/acme_srv.cfg\n          sudo echo \"url_prefix: /foo\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deplyoment-container.yml",
    "content": "name: Deployment Tests - Containers\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Delete acme-sh, letsencypt and lego folders\"\n        run: |\n          sudo rm -rf  lego/*\n          sudo rm -rf  acme-sh/*\n          sudo rm -rf  certbot/*\n          sudo rm -rf  ./*.pem\n\n      - name: \"Test ca_handler_migration\"\n        run: |\n          sudo cp .github/openssl_ca_handler_v16.py examples/Docker/data/ca_handler.py\n          cd examples/Docker/\n          docker compose restart\n          head -n 13 data/ca_handler.py\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          VERIFY_CERT: false\n          REVOCATION: false\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: a2c-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/deplyoment-debian.yml",
    "content": "name: Deployment Tests - Debian Packages\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse NCLM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCLM_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Retrieve Version from version.py\"\n        run: |\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feaature-disablechallengevalidation.yml",
    "content": "name: Feature Tests - Disable challengevalidation\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Install dehydrated\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/dehydrated_install\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Disable challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n\n      - name: \"Enable forward address check\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: True/challenge_validation_disable: True\\nforward_address_check: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Check logs for forward check errors\"\n        working-directory: examples/Docker\n        run: |\n          # check no ip-match\n          docker compose logs 2>&1 | grep \"Forward check failed for www.dynamop.de\"\n          docker compose logs 2>&1 | grep \"SourceAddressValidator._perform_forward_check(): Source address not found in resolved IPs\"\n          # check dns resolution fail\n          docker compose logs 2>&1 | grep \"Forward address check DNS resolution failed: A: NXDOMAIN: lego-unknown.acme does not exist\"\n\n      - name: \"Enable reverse address check\"\n        run: |\n          sudo sed -i \"s/forward_address_check: True/reverse_address_check: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Check logs for reverse check errors\"\n        working-directory: examples/Docker\n        run: |\n          docker compose logs 2>&1 | grep \"Reverse address check failed\"\n          docker compose logs 2>&1 | grep \"Reverse check passed for lego.acme\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n\n      - name: \"EAB - Setup a2c with xca_ca_handler - profiling\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: volume/kid_profiles.json\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json examples/Docker/data/kid_profiles.json\n          sudo sed -i '29,33d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '20,22d' examples/Docker/data/kid_profiles.json\n          sudo chmod 777 examples/eab_handler/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"api_password\\\": \\\"api_password\\\"/\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"forward_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"reverse_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i \"s/  \\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\"\\]/  \\\"allowed_domainlist\\\": \\[\\\"*.acme\\\", \\\"www.example.org\\\"\\]\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"/g\" examples/Docker/data/kid_profiles.json\n\n          sudo sed -i \"s/example.net/acme/g\" examples/Docker/data/kid_profiles.json\n          # sudo sed -i \"s/www.example.com/www.example.local/g\" examples/Docker/data/kid_profiles.json\n          sudo sed -i '28,30d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '14,14d' examples/Docker/data/kid_profiles.json\n          sudo sed -i '8,8d' examples/Docker/data/kid_profiles.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"EAB - forward address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_00\"\n          EAB_HMAC_KEY: \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\"\n\n      - name: \"Check logs for forward check errors\"\n        working-directory: examples/Docker\n        run: |\n          docker compose logs 2>&1 | grep \"Forward address check DNS resolution failed: A: NXDOMAIN: lego-unknown.acme does not exist\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n\n      - name: \"EAB - reverse address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_01\"\n          EAB_HMAC_KEY: \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\"\n\n      - name: \"Check logs for reverse check errors\"\n        working-directory: examples/Docker\n        run: |\n          docker compose logs 2>&1 | grep \"Reverse address check failed\"\n          docker compose logs 2>&1 | grep \"Reverse check passed for lego.acme\"\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n\n      - name: \"EAB - Chellenge validation disabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"false\"\n          EAB_KID: \"keyid_02\"\n          EAB_HMAC_KEY: \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\"\n\n      - name: \"Check logs for challenge validation disabled message\"\n        working-directory: examples/Docker\n        run: |\n          docker compose logs 2>&1 | grep \"Source address checks are disabled. Setting challenge status to valid.\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Install dehydrated\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/dehydrated_install\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Disable challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n\n      - name: \"Enable forward address check\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: True/challenge_validation_disable: True\\nforward_address_check: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"Check logs for forward check errors\"\n        run: |\n          docker exec acme-srv grep \"Forward check failed for www.dynamop.de\" /var/log/messages\n          docker exec acme-srv grep \"SourceAddressValidator._perform_forward_check(): Source address not found in resolved IPs\" /var/log/messages\n          docker exec acme-srv grep \"Forward address check DNS resolution failed: A: NXDOMAIN: lego-unknown.acme does not exist\" /var/log/messages\n\n      - name: \"Enable reverse address check\"\n        run: |\n          sudo sed -i \"s/forward_address_check: True/reverse_address_check: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"Check logs for reverse check errors\"\n        run: |\n          docker exec acme-srv grep \"Reverse address check failed\" /var/log/messages\n          docker exec acme-srv grep \"Reverse check passed for lego.acme\" /var/log/messages\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /opt/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"api_password\\\": \\\"api_password\\\"/\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"forward_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"reverse_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\"\\]/  \\\"allowed_domainlist\\\": \\[\\\"*.acme\\\", \\\"www.example.org\\\"\\]\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"/g\" data/volume/acme_ca/kid_profiles.json\n\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          # sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '28,30d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '14,14d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,8d' data/volume/acme_ca/kid_profiles.json\n          cat data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - forward address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_00\"\n          EAB_HMAC_KEY: \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\"\n\n      - name: \"Check logs for forward check errors\"\n        run: |\n          docker exec acme-srv grep \"Forward address check DNS resolution failed: A: NXDOMAIN: lego-unknown.acme does not exist\" /var/log/messages\n\n      - name: \"EAB - reverse address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_01\"\n          EAB_HMAC_KEY: \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\"\n\n      - name: \"Check logs for reverse check errors\"\n        run: |\n          docker exec acme-srv grep \"Reverse address check failed\" /var/log/messages\n          docker exec acme-srv grep \"Reverse check passed for lego.acme\" /var/log/messages\n\n      - name: \"EAB - Chellenge validation disabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"false\"\n          EAB_KID: \"keyid_02\"\n          EAB_HMAC_KEY: \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\"\n\n      - name: \"Check logs for challenge validation disabled message\"\n        working-directory: examples/Docker\n        run: |\n          docker exec acme-srv grep \"Source address checks are disabled. Setting challenge status to valid.\" /var/log/messages\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Install dehydrated\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/dehydrated_install\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Disable challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n\n      - name: \"Enable forward address check\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: True/challenge_validation_disable: True\\nforward_address_check: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"Enable reverse address check\"\n        run: |\n          sudo sed -i \"s/forward_address_check: True/reverse_address_check: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll\n        with:\n          TO_FAIL: \"true\"\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/kid_profile_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/kid_profiles.json\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_profiling: True\" >> data/volume/acme_srv.cfg\n\n          sudo cp examples/eab_handler/kid_profiles.json data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '29,33d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '20,22d' data/volume/acme_ca/kid_profiles.json\n          sudo chmod 777 data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\[\\\"profile_1\\\", \\\"profile_2\\\", \\\"profile_3\\\"\\]/\\\"template_name\\\"\\: \\[\\\"template\\\", \\\"acme\\\"\\]/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"profile_id\\\"\\: \\\"profile_2\\\"/\\\"template_name\\\"\\: \\\"template\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/\\\"ca_name\\\": \\\"example_ca\\\",/\\\"unknown_key\\\": \\\"unknown_value\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"api_password\\\": \\\"api_password\\\"/\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"forward_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"ca_name\\\": \\\"example_ca_2\\\",/  \\\"issuing_ca_name\\\": \\\"root-ca\\\",\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\",\\n      \\\"reverse_address_check\\\": \\\"True\\\"\\n      \\\"issuing_ca_key\\\": \\\"root-ca\\\"/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i \"s/  \\\"allowed_domainlist\\\": \\[\\\"www.example.com\\\", \\\"www.example.org\\\"\\]/  \\\"allowed_domainlist\\\": \\[\\\"*.acme\\\", \\\"www.example.org\\\"\\]\\n    },\\n    \\\"challenge\\\": {\\n      \\\"challenge_validation_disable\\\": \\\"True\\\"/g\" data/volume/acme_ca/kid_profiles.json\n\n          sudo sed -i \"s/example.net/acme/g\" data/volume/acme_ca/kid_profiles.json\n          # sudo sed -i \"s/www.example.com/www.example.local/g\" data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '28,30d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '14,14d' data/volume/acme_ca/kid_profiles.json\n          sudo sed -i '8,8d' data/volume/acme_ca/kid_profiles.json\n          cat data/volume/acme_ca/kid_profiles.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"EAB - forward address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_00\"\n          EAB_HMAC_KEY: \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\"\n\n      - name: \"EAB - reverse address check enabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"true\"\n          EAB_KID: \"keyid_01\"\n          EAB_HMAC_KEY: \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\"\n\n      - name: \"EAB - Chellenge validation disabled\"\n        uses: ./.github/actions/wf_specific/disable_challengevalidation/enroll_eabprofile\n        with:\n          TO_FAIL: \"false\"\n          EAB_KID: \"keyid_02\"\n          EAB_HMAC_KEY: \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-alpn-challenge.yml",
    "content": "name: Feature Tests - TLS-ALPN-01\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/artifact.tar.gz\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        # if: matrix.execscript == 'rpm_tester.sh'\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-ari.yml",
    "content": "name: Feature Tests - ARI\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777  data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> data/volume/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >>  data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n        with:\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777  data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> data/volume/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >>  data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n        with:\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-dns-challenge.yml",
    "content": "name: Feature Tests - DNS Challenge\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: False\\ndns_server_list: [\\\"DNS-IP\\\"]/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n          sudo cp .github/dns_test.sh acme-sh/\n          docker exec -i acme-sh apk add dnsmasq\n          docker exec -i acme-sh dnsmasq\n          docker exec -i acme-sh mv /acme.sh/dns_test.sh /acmebin/dnsapi/\n          docker exec -i acme-sh chmod +x /acmebin/dnsapi/dns_test.sh\n\n      - name: \"Set DNS server\"\n        run: |\n          cd examples/Docker/\n          docker compose stop\n          docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh\n          sudo sed -i \"s/DNS-IP/$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)/g\" data/acme_srv.cfg\n          docker compose start\n          docker compose logs\n\n      - name: \"Enroll acme.sh - single domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.single --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.single_ecc/acme-sh.single.cer\n\n      - name: \"Enroll acme.sh - two domains\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.first --dns dns_test -d acme-sh.second --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.first_ecc/acme-sh.first.cer\n\n      - name: \"Enroll acme.sh - single wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/*acme-sh.wildcard_ecc/*acme-sh.wildcard.cer\n\n      - name: \"Enroll acme.sh - double wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.first-wildcard --dns dns_test -d *.acme-sh.second-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/*.acme-sh.first-wildcard_ecc/*.acme-sh.first-wildcard.cer\n\n      - name: \"Enroll acme.sh - domain and wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.fqdn-wildcard --dns dns_test -d *.acme-sh.fqdn-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.fqdn-wildcard_ecc/acme-sh.fqdn-wildcard.cer\n\n      - name: \"Check TXT record exists\"\n        if: ${{ failure() }}\n        run: |\n          docker exec -i acme-sh ps -a\n          docker exec -i acme-sh netstat -anu\n          cd examples/Docker/\n          docker compose logs\n          dig -t TXT _acme-challenge.acme-sh.single @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: False\\ndns_server_list: [\\\"DNS-IP\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/\\[CAhandler\\]/\\[CAhandler\\]\\nhandler_file: \\/opt\\/acme2certifier\\/examples\\/ca_handler\\/openssl_ca_handler.py/g\" data/volume/acme_srv.cfg\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n          sudo cp .github/dns_test.sh acme-sh/\n          docker exec -i acme-sh apk add dnsmasq\n          docker exec -i acme-sh dnsmasq\n          docker exec -i acme-sh mv /acme.sh/dns_test.sh /acmebin/dnsapi/\n          docker exec -i acme-sh chmod +x /acmebin/dnsapi/dns_test.sh\n\n      - name: \"set DNS server\"\n        run: |\n          docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh\n          sudo sed -i \"s/DNS-IP/$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Enroll acme.sh - single domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.single --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.single_ecc/acme-sh.single.cer\n\n      - name: \"Enroll acme.sh - two domains\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.first --dns dns_test -d acme-sh.second --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.first_ecc/acme-sh.first.cer\n\n      - name: \"Enroll acme.sh - single wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/*acme-sh.wildcard_ecc/*acme-sh.wildcard.cer\n\n      - name: \"Enroll acme.sh - double wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.first-wildcard --dns dns_test -d *.acme-sh.second-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/*.acme-sh.first-wildcard_ecc/*.acme-sh.first-wildcard.cer\n\n      - name: \"Enroll acme.sh - domain and wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.fqdn-wildcard --dns dns_test -d *.acme-sh.fqdn-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.fqdn-wildcard_ecc/acme-sh.fqdn-wildcard.cer\n\n      - name: \"Check TXT record exists\"\n        if: ${{ failure() }}\n        run: |\n          docker exec -i acme-sh ps -a\n          docker exec -i acme-sh netstat -anu\n          cd examples/Docker/\n          docker compose logs\n          dig -t TXT _acme-challenge.acme-sh.single @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: False\\ndns_server_list: [\\\"DNS-IP\\\"]/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/\\[CAhandler\\]/\\[CAhandler\\]\\nhandler_file: \\/var\\/www\\/acme2certifier\\/examples\\/ca_handler\\/openssl_ca_handler.py/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n          sudo cp .github/dns_test.sh acme-sh/\n          docker exec -i acme-sh apk add dnsmasq\n          docker exec -i acme-sh dnsmasq\n          docker exec -i acme-sh mv /acme.sh/dns_test.sh /acmebin/dnsapi/\n          docker exec -i acme-sh chmod +x /acmebin/dnsapi/dns_test.sh\n\n      - name: \"set DNS server\"\n        run: |\n          docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh\n          sudo sed -i \"s/DNS-IP/$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Enroll acme.sh - single domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.single --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.single_ecc/acme-sh.single.cer\n\n      - name: \"Enroll acme.sh - two domains\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.first --dns dns_test -d acme-sh.second --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.first_ecc/acme-sh.first.cer\n\n      - name: \"Enroll acme.sh - single wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/*acme-sh.wildcard_ecc/*acme-sh.wildcard.cer\n\n      - name: \"Enroll acme.sh - double wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d *.acme-sh.first-wildcard --dns dns_test -d *.acme-sh.second-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/*.acme-sh.first-wildcard_ecc/*.acme-sh.first-wildcard.cer\n\n      - name: \"Enroll acme.sh - domain and wildcard domain\"\n        run: |\n          docker exec -i acme-sh acme.sh --dnssleep 10 --server http://acme-srv --accountemail 'acme-sh@example.com' --issue --dns dns_test -d acme-sh.fqdn-wildcard --dns dns_test -d *.acme-sh.fqdn-wildcard --standalone --debug 3 --output-insecure --force  # NOSONAR\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.fqdn-wildcard_ecc/acme-sh.fqdn-wildcard.cer\n\n      - name: \"Check TXT record exists\"\n        if: ${{ failure() }}\n        run: |\n          docker exec -i acme-sh ps -a\n          docker exec -i acme-sh netstat -anu\n          cd examples/Docker/\n          docker compose logs\n          dig -t TXT _acme-challenge.acme-sh.single @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.first-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n          dig -t TXT _acme-challenge.acme-sh.second-wildcard @$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' acme-sh)\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-dryrun.yml",
    "content": "name: Feature Tests - ARI\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier1'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier1'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777  data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> data/volume/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >>  data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n        with:\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo cp -rp certbot/ ${{ github.workspace }}/artifact/certbot/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh certbot lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier1'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777  data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Renewalinfo]\" >> data/volume/acme_srv.cfg\n          sudo echo \"renewal_force: True\" >>  data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/wf_specific/ari/enroll\n        with:\n          CA_PATH: data/volume/acme_ca/\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-eab.yml",
    "content": "name: Feature Tests - EAB\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Create letsencrypt folder\"\n        run: |\n          mkdir -p certbot\n          mkdir -p lego\n          mkdir -p acme-sh\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp examples/eab_handler/key_file.json examples/Docker/data/acme_ca\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/json_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/key_file.json\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Register without eab-credentials - should fail\"\n        uses: ./.github/actions/wf_specific/eab/enroll_wo_credentials\n\n      - name: \"Register with unknown eab-credentials - should fail\"\n        uses: ./.github/actions/wf_specific/eab/enroll_unknown_credentials\n        with:\n          EAB_KEY_ID: \"unknown_key_id\"\n          EAB_KEY_SECRET: \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\"\n\n      - name: \"Register with wrong eab-credentials - should fail\"\n        uses: ./.github/actions/wf_specific/eab/enroll_wrong_credentials\n        with:\n          EAB_KEY_ID: \"keyid_01\"\n          EAB_KEY_SECRET: \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\"\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Register acme.sh with eab-credentials\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Rename eab-kid\"\n        run: |\n          sudo sed -i \"s/keyid_02/keyid_12/g\" examples/Docker/data/acme_ca/key_file.json\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Enroll acme.sh with unknown kid - should fail\"\n        id: acmekidfail\n        continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result \"\n        if: steps.acmekidfail.outcome != 'failure'\n        run: |\n          echo \"certbot outcome is ${{steps.acmekidfail.outcome }}\"\n          exit 1\n\n      - name: \"Disable eab-kid checking\"\n        run: |\n          sudo echo \"eabkid_check_disable: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Enroll acme.sh with unknown kid - should work\"\n        # id: acmekidfail\n        # continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create letsencrypt folder\"\n        run: |\n          mkdir -p certbot\n          mkdir -p lego\n          mkdir -p acme-sh\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp examples/eab_handler/key_file.json data/volume/acme_ca\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /opt/acme2certifier/examples/eab_handler/json_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /opt/acme2certifier/volume/acme_ca/key_file.json\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Fail - Register lego\"\n        id: legofail\n        continue-on-error: true\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n\n      - name: \"Check lego result\"\n        if: steps.legofail.outcome != 'failure'\n        run: |\n          echo \"legofail outcome is ${{steps.legofail.outcome }}\"\n          exit 1\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Fail - Registercertbot without eab-credentials\"\n        id: certbotfail\n        continue-on-error: true\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"check certbot result \"\n        if: steps.certbotfail.outcome != 'failure'\n        run: |\n          echo \"certbot outcome is ${{steps.certbotfail.outcome }}\"\n          exit 1\n\n      - name: \"Register certbot using eab-credentials\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email --eab-kid keyid_02 --eab-hmac-key=dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\n\n      - name: \"Enroll HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n\n      - name: \"Fail - Register acme.sh\"\n        id: acmeshfail\n        continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Check acme.sh result \"\n        if: steps.acmeshfail.outcome != 'failure'\n        run: |\n          echo \"acmeshfail outcome is ${{steps.acmeshfail.outcome }}\"\n          exit 1\n\n      - name: \"Register acme.sh with eab-credentials\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Rename eab-kid\"\n        run: |\n          sudo sed -i \"s/keyid_02/keyid_12/g\" data/volume/acme_ca/key_file.json\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh with unknown kid - should fail\"\n        id: acmekidfail\n        continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result \"\n        if: steps.acmekidfail.outcome != 'failure'\n        run: |\n          echo \"certbot outcome is ${{steps.acmekidfail.outcome }}\"\n          exit 1\n\n      - name: \"Disable eab-kid checking\"\n        run: |\n          sudo echo \"eabkid_check_disable: True\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh with unknown kid - should work\"\n        # id: acmekidfail\n        # continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create letsencrypt folder\"\n        run: |\n          mkdir -p certbot\n          mkdir -p lego\n          mkdir -p acme-sh\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp examples/eab_handler/key_file.json data/volume/acme_ca\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[EABhandler]\" >> data/volume/acme_srv.cfg\n          sudo echo \"eab_handler_file: /var/www/acme2certifier/examples/eab_handler/json_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"key_file: /var/www/acme2certifier/volume/acme_ca/key_file.json\" >> data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Fail - Register lego\"\n        id: legofail\n        continue-on-error: true\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n\n      - name: \"Check lego result\"\n        if: steps.legofail.outcome != 'failure'\n        run: |\n          echo \"legofail outcome is ${{steps.legofail.outcome }}\"\n          exit 1\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --eab --kid keyid_02 --hmac dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Fail - Registercertbot without eab-credentials\"\n        id: certbotfail\n        continue-on-error: true\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"check certbot result \"\n        if: steps.certbotfail.outcome != 'failure'\n        run: |\n          echo \"certbot outcome is ${{steps.certbotfail.outcome }}\"\n          exit 1\n\n      - name: \"Register certbot using eab-credentials\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email --eab-kid keyid_02 --eab-hmac-key=dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\n\n      - name: \"Enroll HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem certbot/live/certbot/cert.pem\n\n      - name: \"Fail - Register acme.sh\"\n        id: acmeshfail\n        continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Check acme.sh result \"\n        if: steps.acmeshfail.outcome != 'failure'\n        run: |\n          echo \"acmeshfail outcome is ${{steps.acmeshfail.outcome }}\"\n          exit 1\n\n      - name: \"Register acme.sh with eab-credentials\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --register-account --server http://acme-srv --accountemail 'acme-sh@example.com' --eab-kid keyid_02 --eab-hmac-key dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM --debug 3\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem  acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Rename eab-kid\"\n        run: |\n          sudo sed -i \"s/keyid_02/keyid_12/g\" data/volume/acme_ca/key_file.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh with unknown kid - should fail\"\n        id: acmekidfail\n        continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result \"\n        if: steps.acmekidfail.outcome != 'failure'\n        run: |\n          echo \"certbot outcome is ${{steps.acmekidfail.outcome }}\"\n          exit 1\n\n      - name: \"Disable eab-kid checking\"\n        run: |\n          sudo echo \"eabkid_check_disable: True\" >> data/volume/acme_srv.cfg\n\n      - name: \"Rename eab-kid\"\n        run: |\n          sudo sed -i \"s/keyid_02/keyid_12/g\" data/volume/acme_ca/key_file.json\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n\n      - name: \"Enroll acme.sh with unknown kid - should work\"\n        # id: acmekidfail\n        # continue-on-error: true\n        run: |\n          docker run --rm -i -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest --issue --server http://acme-srv -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-emailreply-challenge.yml",
    "content": "name: Feature Tests - EmailReply Challenge\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Install mailserver\"\n        uses: ./.github/actions/mailserver_install\n        with:\n          MAILSERVER_CERT: ${{ secrets.MAILSERVER_CERT }}\n\n      - name: \"Setup a2c with xca_ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          # sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> examples/Docker/data/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nemail_identifier_support: True\\nemail_identifier_rewrite: True\\ndns_validation_pause_timer: 5/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nimap_server: mailserver.acme\\nimap_port: 993\\nimap_use_ssl: True\\nsmtp_port: 587\\nsmtp_use_tls: True\\nusername: a2c@mailserver.acme\\npassword: a2cstarter\\nemail_address: a2c@mailserver.acme/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Install and enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          INSTALL: \"true\"\n          TO_FAIL: \"true\"\n\n      - name: \"Disable Challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          TO_FAIL: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp mailserver/ ${{ github.workspace }}/artifact/mailserver/\n          sudo mkdir -p ${{ github.workspace }}/artifact/acme_email\n          sudo cp acme_email/lets* ${{ github.workspace }}/artifact/acme_email/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          docker logs mailserver 2>&1 > ${{ github.workspace }}/artifact/mailserver.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log mailserver.log data acme_email mailserver\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Install mailserver\"\n        uses: ./.github/actions/mailserver_install\n        with:\n          MAILSERVER_CERT: ${{ secrets.MAILSERVER_CERT }}\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nemail_identifier_support: True\\nemail_identifier_rewrite: True\\ndns_validation_pause_timer: 5/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nimap_server: mailserver.acme\\nimap_port: 993\\nimap_use_ssl: True\\nsmtp_port: 587\\nsmtp_use_tls: True\\nusername: a2c@mailserver.acme\\npassword: a2cstarter\\nemail_address: a2c@mailserver.acme/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n          docker ps -a\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Install and enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          INSTALL: \"true\"\n          TO_FAIL: \"true\"\n\n      - name: \"Disable challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          TO_FAIL: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          sudo cp -rp mailserver/ ${{ github.workspace }}/artifact/mailserver/\n          sudo mkdir -p ${{ github.workspace }}/artifact/acme_email\n          # sudo cp acme_email/lets* ${{ github.workspace }}/artifact/acme_email/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log data acme_email mailserver\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Install mailserver\"\n        uses: ./.github/actions/mailserver_install\n        with:\n          MAILSERVER_CERT: ${{ secrets.MAILSERVER_CERT }}\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/examples/ca_handler/xca_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"allowed_domainlist: [\\\"bar.local\\\", \\\"*.acme\\\"]\" >> data/volume/acme_srv.cfg\n\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nemail_identifier_support: True\\nemail_identifier_rewrite: True\\ndns_validation_pause_timer: 5/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nimap_server: mailserver.acme\\nimap_port: 993\\nimap_use_ssl: True\\nsmtp_port: 587\\nsmtp_use_tls: True\\nusername: a2c@mailserver.acme\\npassword: a2cstarter\\nemail_address: a2c@mailserver.acme/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"true\"\n\n      - name: \"Install and enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          INSTALL: \"true\"\n          TO_FAIL: \"true\"\n\n      - name: \"Disable challenge validation\"\n        run: |\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acme-email\"\n        uses: ./.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll\n        with:\n          TO_FAIL: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-enrollment-timeout.yml",
    "content": "name: Feature Tests - Asynchronous enrollment and certificate re-usage\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # -----------------------------------------------------------\n  # Test container images - enrollment timeout and cert re-usage\n  # -----------------------------------------------------------\n  test-containers-timeout-cert-reusage:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n          DJANGO_DB: psql\n\n      - name: \"create folders\"\n        run: |\n          mkdir lego\n          mkdir acme-sh\n          mkdir certbot\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo chmod 777 examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/import uuid/import uuid\\\\nimport time/g\" examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        time.sleep(30)/g\" examples/Docker/data/ca_handler.py\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 300\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Enrollment\"\n        uses: ./.github/actions/wf_specific/enrollment_timeout/enroll\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-timeout-cert-reusage-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # -----------------------------------------------------------\n  # Test container images - django async enrollment\n  # -----------------------------------------------------------\n  test-containers-async:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['django']\n        django_db: ['psql', 'mariadb']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: django\n          WEB_SRV: ${{ matrix.websrv }}\n          DJANGO_DB: ${{ matrix.django_db }}\n          CONTAINER_BUILD: false\n\n      - name: \"create folders\"\n        run: |\n          mkdir lego\n          mkdir acme-sh\n          mkdir certbot\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo chmod 777 examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/import uuid/import uuid\\\\nimport time/g\" examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        time.sleep(20)/g\" examples/Docker/data/ca_handler.py\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 300\\ncert_operations_log: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nasync_mode: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check logs for async enrollment messages\"\n        working-directory: examples/Docker/\n        run: |\n            docker compose logs | grep \"asynchronous Challenge validation enabled, not waiting for result\"\n            docker compose logs | grep \"asynchronous enrollment started\"\n\n      - name: \"[ * ] collecting test data\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-async-${{ matrix.websrv }}-${{ matrix.django_db }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # -------------------------------------------------------------\n  # Test RPMs (EL8 & EL9)  - enrollment timeout and cert re-usage\n  # -------------------------------------------------------------\n  test-rpm-timeout-cert-reusage:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp examples/ca_handler/openssl_ca_handler.py data/volume/acme_ca/ca_handler.py\n          sudo chmod 777 data/volume/acme_ca/ca_handler.py\n          sudo sed -i \"s/import uuid/import uuid\\\\nimport time/g\" data/volume/acme_ca/ca_handler.py\n          # sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        time.sleep(15)/g\" data/volume/acme_ca/ca_handler.py\n          sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        self.logger.debug('CAhandler.enroll(): timeout start')\\\\n        time.sleep(30)\\\\n        self.logger.debug('CAhandler.enroll(): timeout done')/g\" data/volume/acme_ca/ca_handler.py\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" data/volume/acme_srv.cfg\n          # sudo sed -i \"s/retry_after_timeout: 15/retry_after_timeout: 30\\nenrollment_timeout: 15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/handler_file: examples\\/ca_handler\\/openssl_ca_handler.py/handler_file: \\/opt\\/acme2certifier\\/volume\\/acme_ca\\/ca_handler.py/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enrollment\"\n        uses: ./.github/actions/wf_specific/enrollment_timeout/enroll\n        with:\n          DEPLOYMENT_TYPE: \"rpm\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec postgresdbsrv pg_dump -U postgres  acme2certifier  > data/acme2certifier.sql\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv rpm -qa > ${{ github.workspace }}/artifact/data/packages.txt\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-timeout-cert-reusage-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # -------------------------------------------------------------\n  # Test RPMs (EL8 & EL9)  - async enrollment\n  # -------------------------------------------------------------\n  test-rpm-async:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['django_tester.sh']\n        django_db: ['psql', 'mariadb']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          DJANGO_DB: ${{ matrix.django_db }}\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp examples/ca_handler/openssl_ca_handler.py data/volume/acme_ca/ca_handler.py\n          sudo chmod 777 data/volume/acme_ca/ca_handler.py\n          sudo sed -i \"s/import uuid/import uuid\\\\nimport time/g\" data/volume/acme_ca/ca_handler.py\n          # sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        time.sleep(15)/g\" data/volume/acme_ca/ca_handler.py\n          sudo sed -i \"s/        cert_raw = None/        cert_raw = None\\\\n        self.logger.debug('CAhandler.enroll(): timeout start')\\\\n        time.sleep(20)\\\\n        self.logger.debug('CAhandler.enroll(): timeout done')/g\" data/volume/acme_ca/ca_handler.py\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\ncert_reusage_timeframe: 1800\\nenrollment_timeout: 15\\ncert_operations_log: True/g\" data/volume/acme_srv.cfg\n          # sudo sed -i \"s/retry_after_timeout: 15/retry_after_timeout: 30\\nenrollment_timeout: 15/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/handler_file: examples\\/ca_handler\\/openssl_ca_handler.py/handler_file: \\/opt\\/acme2certifier\\/volume\\/acme_ca\\/ca_handler.py/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nasync_mode: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Check logs for async enrollment messages\"\n        working-directory: examples/Docker/\n        run: |\n            docker exec -i acme-srv cat /var/log/messages | grep \"asynchronous Challenge validation enabled, not waiting for result\"\n            docker exec -i acme-srv cat /var/log/messages | grep \"asynchronous enrollment started\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/nginx.tgz /etc/nginx\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv rpm -qa > ${{ github.workspace }}/artifact/data/packages.txt\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-async-rh${{ matrix.rhversion }}-${{ matrix.django_db }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-headerinfo.yml",
    "content": "name: Feature Tests - Headerinfo\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Create folders\"\n        run: |\n          mkdir lego\n          mkdir acme-sh\n          mkdir certbot\n\n\n      - name: \"Setup a2c with xca_ca_handler\"\n        run: |\n          sudo cp examples/ca_handler/xca_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo chmod 777 examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/error = eab_profile_header_info_check(self.logger, self, csr, \\\"template_name\\\")/qset = header_info_get(self.logger, csr=csr)\\n            if qset:\\n                self.logger.info('customized header_info: %s', qset[-1]['header_info'])/g\" examples/Docker/data/ca_handler.py\n          sudo sed -i \"s/eab_profile_header_info_check/header_info_get/g\" examples/Docker/data/ca_handler.py\n          sudo mkdir -p examples/Docker/data/xca\n          sudo chmod -R 777 examples/Docker/data/xca\n          sudo cp test/ca/acme2certifier-clean.xdb examples/Docker/data/xca/$XCA_DB_NAME\n\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo touch examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"xdb_file: volume/xca/$XCA_DB_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent foo-bar-doo  -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt | awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"check header info\"\n        run: |\n          cd examples/Docker/\n          docker compose logs | grep foo-bar-doo | grep customized\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: hcontainer-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create lego folder\"\n        run: |\n          mkdir lego\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n\n          sudo cp examples/ca_handler/xca_ca_handler.py data/volume/ca_handler.py\n          sudo chmod 777 data/volume/ca_handler.py\n          sudo sed -i \"s/error = eab_profile_header_info_check(self.logger, self, csr, \\\"template_name\\\")/qset = header_info_get(self.logger, csr=csr)\\n            if qset:\\n                self.logger.info('customized header_info: %s', qset[-1]['header_info'])/g\" data/volume/ca_handler.py\n          sudo sed -i \"s/eab_profile_header_info_check/header_info_get/g\" data/volume/ca_handler.py\n\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/volume/ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent foo-bar-doo  -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"check header info\"\n        run: |\n          docker exec acme-srv grep foo-bar-doo /var/log/messages | grep customized\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create lego folder\"\n        run: |\n          mkdir lego\n\n      - name: \"Setup acme_srv.cfg with xca_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/acme2certifier-clean.xdb data/volume/acme_ca/$XCA_DB_NAME\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n\n          sudo cp examples/ca_handler/xca_ca_handler.py data/volume/ca_handler.py\n          sudo chmod 777 data/volume/ca_handler.py\n          sudo sed -i \"s/error = eab_profile_header_info_check(self.logger, self, csr, \\\"template_name\\\")/qset = header_info_get(self.logger, csr=csr)\\n            if qset:\\n                self.logger.info('customized header_info: %s', qset[-1]['header_info'])/g\" data/volume/ca_handler.py\n          sudo sed -i \"s/eab_profile_header_info_check/header_info_get/g\" data/volume/ca_handler.py\n\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /var/www/acme2certifier/volume/ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"xdb_file: /var/www/acme2certifier/volume/acme_ca/$XCA_DB_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"issuing_ca_name: $XCA_ISSUING_CA\" >> data/volume/acme_srv.cfg\n          sudo echo \"passphrase: $XCA_PASSPHRASE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_cert_chain_list: [\\\"root-ca\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo \"template_name: $XCA_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nheader_info_list: [\\\"HTTP_USER_AGENT\\\"]/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" --user-agent foo-bar-doo  -d lego.acme --http run\n          sudo cat lego/certificates/lego.acme.issuer.crt |  awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}'\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n\n      - name: \"Sleep for 15s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 15s\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-hooks.yml",
    "content": "name: Feature Tests - Hooks\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images - regular hooks\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/hooks\n          sudo chmod -R 777 examples/Docker/data/hooks\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Hooks]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"hooks_file: /var/www/acme2certifier/examples/hooks/cn_dump_hooks.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"save_path: volume/hooks\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"$HOOKS_CHECKSUM\" > examples/Docker/data/hooks/checksums.sha256\n          # sudo cat examples/Docker/data/acme_srv.cfg\n        env:\n          HOOKS_CHECKSUM: ${{ secrets.HOOKS_CHECKSUM }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Register certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"Enroll certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Register acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --register-account --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Compare checksums to validate hook file content\"\n        working-directory: examples/Docker/data/hooks\n        run: |\n          sha256sum -c checksums.sha256\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-hooks-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images - hooks exception handling\n  # ---------------------------------------------------------\n  test-containers-exception-handling:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/hooks\n          sudo chmod -R 777 examples/Docker/data/hooks\n          sudo cp examples/ca_handler/openssl_ca_handler.py examples/Docker/data/ca_handler.py\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Hooks]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"hooks_file: /var/www/acme2certifier/examples/hooks/exception_test_hooks.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"raise_pre_hook_exception: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"raise_post_hook_exception: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"raise_success_hook_exception: False\" >> examples/Docker/data/acme_srv.cfg\n          # sudo cat examples/Docker/data/acme_srv.cfg\n        env:\n          HOOKS_CHECKSUM: ${{ secrets.HOOKS_CHECKSUM }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Rrepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Registeracme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --register-account --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Enroll acme.sh - *_pre_hook_failure not configured \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger pre hook exception \"\n        run: |\n          sudo sed -i \"s/raise_pre_hook_exception: False/raise_pre_hook_exception: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"acme.sh enrollment fails due to pre-hook exception (default behaviour)\"\n        id: prehookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to pre-hook exception \"\n        if: steps.prehookfailure.outcome != 'failure'\n        run: |\n          echo \"prehookfailure outcome is ${{steps.prehookfailure.outcome }}\"\n          exit 1\n\n      - name: \"Reconfigure a2c to ignore pre-hook failures \"\n        run: |\n          sudo echo \"ignore_pre_hook_failure: True\" >> examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"Enroll acme.sh - ignore pre_hook_failures \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger success hook exception \"\n        run: |\n          sudo sed -i \"s/raise_pre_hook_exception: True/raise_pre_hook_exception: False/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/raise_success_hook_exception: False/raise_success_hook_exception: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"acme.sh enrollment fails due to success-hook exception (default behaviour) \"\n        id: successhookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to success-hook exception \"\n        if: steps.successhookfailure.outcome != 'failure'\n        run: |\n          echo \"successhookfailure outcome is ${{steps.successhookfailure.outcome }}\"\n          exit 1\n\n      - name: \"Reconfigure a2c to ignore success-hook failures \"\n        run: |\n          sudo sed -i \"s/ignore_pre_hook_failure: True/ignore_success_hook_failure: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"Enroll acme.sh - ignore sucess_hook_failures \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger post hook exception \"\n        run: |\n          sudo sed -i \"s/raise_success_hook_exception: True/raise_success_hook_exception: False/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/raise_post_hook_exception: False/raise_post_hook_exception: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"Enroll acme.sh - ignore post_hook_failures (default behaviour) \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure a2c to detect success-hook failures \"\n        run: |\n          sudo sed -i \"s/ignore_success_hook_failure: True/ignore_post_hook_failure: False/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' acme2certifier-acme-srv-1)\n          docker compose restart\n\n      - name: \"acme.sh enrollment fails due to post-hook exception \"\n        id: posthookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to post-hook exception \"\n        if: steps.posthookfailure.outcome != 'failure'\n        run: |\n          echo \"posthookfailure outcome is ${{steps.posthookfailure.outcome }}\"\n          exit 1\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-exception-handling-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test container images - email hooks\n  # ---------------------------------------------------------\n  test-containers-email-hooks:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Install mailserver\"\n        uses: ./.github/actions/mailserver_install\n        with:\n          MAILSERVER_CERT: ${{ secrets.MAILSERVER_CERT }}\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/hooks\n          sudo chmod -R 777 examples/Docker/data/hooks\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Hooks]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"hooks_file: /var/www/acme2certifier/examples/hooks/email_hooks.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"report_failures: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"report_successes: True\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"subject_prefix: [ACME]\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"rcpt: jum@mailserver.acme\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nappname: acme2certifier\\nsmtp_server: mailserver.acme\\nsmtp_port: 465\\nsmtp_use_tls: True\\nconnection_timeout: 30\\nusername: jum@mailserver.acme\\npassword: jumstarter\\nemail_address: a2c@mailserver.acme\\n/g\" examples/Docker/data/acme_srv.cfg\n        env:\n          HOOKS_CHECKSUM: ${{ secrets.HOOKS_CHECKSUM }}\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Enrollment tests\"\n        uses: ./.github/actions/wf_specific/hooks/enroll\n        with:\n          DEPLOYMENT_TYPE: container\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-email-hooks-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"create letsencrypt and lego folder\"\n        run: |\n          mkdir acme-sh\n          mkdir lego\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo mkdir -p data/volume/acme_ca/hooks\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo chmod -R 777 data/volume/acme_ca/hooks\n          sudo echo -e \"\\n\\n[Hooks]\" >> data/volume/acme_srv.cfg\n          sudo echo \"hooks_file: /opt/acme2certifier/examples/hooks/cn_dump_hooks.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"save_path: /tmp/acme2certifier/volume/acme_ca/hooks\" >> data/volume/acme_srv.cfg\n          sudo echo \"$HOOKS_CHECKSUM\" > data/volume/acme_ca/hooks/checksums.sha256\n        env:\n          HOOKS_CHECKSUM: ${{ secrets.HOOKS_CHECKSUM }}\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Register certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot register --agree-tos -m 'certbot@example.com' --server http://acme-srv --no-eff-email\n\n      - name: \"Enroll HTTP-01 single domain certbot\"\n        run: |\n          docker run -i --rm --name certbot --network acme -v $PWD/certbot:/etc/letsencrypt/ certbot/certbot certonly --server http://acme-srv --standalone --preferred-challenges http -d certbot.acme --cert-name certbot\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem certbot/live/certbot/cert.pem\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Register acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --register-account --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Enroll lego\"\n        run: |\n          docker run -i -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme --http run\n          sudo openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem lego/certificates/lego.acme.crt\n\n      - name: \"Compare checksums to validate hook file content\"\n        working-directory: data/volume/acme_ca/hooks\n        run: |\n          sha256sum -c checksums.sha256\n\n      - name: \"Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) hooks exception handling\n  # ---------------------------------------------------------\n  test-rpm-hooks-exception-handling:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"create letsencrypt and lego folder\"\n        run: |\n          mkdir acme-sh\n          mkdir lego\n\n      - name: \"prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo mkdir -p data/volume/acme_ca/hooks\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo chmod -R 777 data/volume/acme_ca/hooks\n          sudo echo -e \"\\n\\n[Hooks]\" >> data/volume/acme_srv.cfg\n          sudo echo \"hooks_file: /opt/acme2certifier/examples/hooks/exception_test_hooks.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"raise_pre_hook_exception: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"raise_post_hook_exception: False\" >> data/volume/acme_srv.cfg\n          sudo echo \"raise_success_hook_exception: False\" >> data/volume/acme_srv.cfg\n        env:\n          HOOKS_CHECKSUM: ${{ secrets.HOOKS_CHECKSUM }}\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Register acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --register-account --accountemail 'acme-sh@example.com' --debug 3\n\n      - name: \"Enroll acme.sh - *_pre_hook_failure not configured \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --debug 3 --output-insecure\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger pre hook exception \"\n        run: |\n          sudo sed -i \"s/raise_pre_hook_exception: False/raise_pre_hook_exception: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"acme.sh enrollment fails due to pre-hook exception (default behaviour)\"\n        id: prehookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to pre-hook exception \"\n        if: steps.prehookfailure.outcome != 'failure'\n        run: |\n          echo \"prehookfailure outcome is ${{steps.prehookfailure.outcome }}\"\n          exit 1\n\n      - name: \"Reconfigure a2c to ignore pre-hook failures \"\n        run: |\n          sudo echo \"ignore_pre_hook_failure: True\" >> data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh - ignore pre_hook_failures \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger success hook exception \"\n        run: |\n          sudo sed -i \"s/raise_pre_hook_exception: True/raise_pre_hook_exception: False/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/raise_success_hook_exception: False/raise_success_hook_exception: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"acme.sh enrollment fails due to success-hook exception (default behaviour) \"\n        id: successhookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to success-hook exception \"\n        if: steps.successhookfailure.outcome != 'failure'\n        run: |\n          echo \"successhookfailure outcome is ${{steps.successhookfailure.outcome }}\"\n          exit 1\n\n      - name: \"Reconfigure a2c to ignore success-hook failures \"\n        run: |\n          sudo sed -i \"s/ignore_pre_hook_failure: True/ignore_success_hook_failure: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh - ignore sucess_hook_failures \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure hook handler to trigger post hook exception \"\n        run: |\n          sudo sed -i \"s/raise_success_hook_exception: True/raise_success_hook_exception: False/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/raise_post_hook_exception: False/raise_post_hook_exception: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll acme.sh - ignore post_hook_failures (default behaviour) \"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Reconfigure a2c to detect success-hook failures \"\n        run: |\n          sudo sed -i \"s/ignore_success_hook_failure: True/ignore_post_hook_failure: False/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Fail acme.sh enrollment fails due to post-hook exception \"\n        id: posthookfailure\n        continue-on-error: true\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --force --debug 3 --output-insecure\n\n      - name: \"Check result - acme.sh enrollment failed due to post-hook exception \"\n        if: steps.posthookfailure.outcome != 'failure'\n        run: |\n          echo \"posthookfailure outcome is ${{steps.posthookfailure.outcome }}\"\n          exit 1\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-hooks-exception-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) email hooks\n  # ---------------------------------------------------------\n  test-rpm-hooks-email-hooks:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Install mailserver\"\n        uses: ./.github/actions/mailserver_install\n        with:\n          MAILSERVER_CERT: ${{ secrets.MAILSERVER_CERT }}\n          DEPLOYMENT_TYPE: rpm\n\n      - name: \"prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo mkdir -p data/volume/acme_ca/hooks\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo chmod -R 777 data/volume/acme_ca/hooks\n          echo \"allowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]\" >> data/volume/acme_srv.cfg\n          sudo echo -e \"\\n\\n[Hooks]\" >> data/volume/acme_srv.cfg\n          sudo echo \"hooks_file: /opt/acme2certifier/examples/hooks/email_hooks.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"report_failures: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"report_successes: True\" >> data/volume/acme_srv.cfg\n          sudo echo \"subject_prefix: [ACME]\" >> data/volume/acme_srv.cfg\n          sudo echo \"rcpt: jum@mailserver.acme\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nappname: acme2certifier\\nsmtp_server: mailserver.acme\\nsmtp_port: 465\\nsmtp_use_tls: True\\nconnection_timeout: 30\\nusername: jum@mailserver.acme\\npassword: jumstarter\\nemail_address: a2c@mailserver.acme\\n/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enrollment tests\"\n        uses: ./.github/actions/wf_specific/hooks/enroll\n        with:\n          DEPLOYMENT_TYPE: rpm\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-email-hooks-rh${{ matrix.rhversion }}-{{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-idempotent-finalize.yml",
    "content": "name: Feature Tests - Idempotent_finalize option tests\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse NCLM configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.NCLM_CFG }}\n\n      - name: \"Generate UUID\"\n        run: |\n          echo UUID=$(uuidgen | cut -d \"-\" -f1) >> $GITHUB_ENV\n      - run: echo \"UUID ${{ env.UUID }}\"\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"false\"\n          ALMA_START: \"true\"\n\n      - name: \"Update configuration - add idempotent_finalize parameter\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nidempotent_finalize: True/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"true\"\n          ALMA_START: \"false\"\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker-compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"false\"\n          ALMA_START: \"true\"\n\n      - name: \"Update configuration - add idempotent_finalize parameter\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nidempotent_finalize: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT  restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"true\"\n          ALMA_START: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp acmeshell ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/challenge_validation_disable: False/challenge_validation_disable: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"false\"\n          ALMA_START: \"true\"\n\n      - name: \"Update configuration - add idempotent_finalize parameter\"\n        run: |\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: False\\nidempotent_finalize: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSRIPT restart $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via acmeshell\"\n        uses: ./.github/actions/acmeshell\n        with:\n          IDEMPOTENT_FINALIZE: \"true\"\n          ALMA_START: \"false\"\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-ipaddress-identifier.yml",
    "content": "name: Feature Tests - IP addresses identifier\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"[ PREPARE ] get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Enroll HTTP-01 single domain and ip address \"\n        run: |\n          sudo rm -rf lego/*\n          docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme -d $RUNNER_IP --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n          sudo openssl x509 -in lego/certificates/lego.acme.crt --text --noout | grep \"IP Address\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"[ PREPARE ] get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Enroll HTTP-01 single domain and ip address \"\n        run: |\n          docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme -d $RUNNER_IP --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n          sudo openssl x509 -in lego/certificates/lego.acme.crt --text --noout | grep \"IP Address\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo cp -rp lego/ ${{ github.workspace }}/artifact/lego/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log lego\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"[ PREPARE ] get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/\\[CAhandler\\]/\\[CAhandler\\]\\nhandler_file: \\/var\\/www\\/acme2certifier\\/examples\\/ca_handler\\/openssl_ca_handler.py/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test enrollment\"\n        uses: ./.github/actions/acme_clients\n\n      - name: \"Enroll HTTP-01 single domain and ip address \"\n        run: |\n          docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://acme-srv -a --email \"lego@example.com\" -d lego.acme -d $RUNNER_IP --http run\n          sudo openssl verify -CAfile cert-2.pem -untrusted cert-1.pem lego/certificates/lego.acme.crt\n          sudo openssl x509 -in lego/certificates/lego.acme.crt --text --noout | grep \"IP Address\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-ipv6.yml",
    "content": "name: Feature Tests - IPv6\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n          IPV6: true\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v6 --debug 3 --output-insecure\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n\n      - name: \"Enroll acme.sh using ipv6 with ipv4 fallback\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v4 --debug 3 --output-insecure --force\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme${ECC}/acme-sh.acme.cer\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n          IPV6: true\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create lego and certbot folder\"\n        run: |\n          mkdir lego\n          mkdir certbot\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v6 --debug 3 --output-insecure\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Enroll acme.sh using ipv6 with ipv4 fallback\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v4 --debug 3 --output-insecure --force\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/artifact.tar.gz\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh','django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse XCA configuration from JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.XCA_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Create lego and certbot folder\"\n        run: |\n          mkdir lego\n          mkdir certbot\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/\\[CAhandler\\]/\\[CAhandler\\]\\nhandler_file: \\/var\\/www\\/acme2certifier\\/examples\\/ca_handler\\/openssl_ca_handler.py/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume/\\/var\\/www\\/acme2certifier\\/volume/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Enroll acme.sh\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v6 --debug 3 --output-insecure\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Enroll acme.sh using ipv6 with ipv4 fallback\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --keylength 2048 --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --listen-v4 --debug 3 --output-insecure --force\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-proxy.yml",
    "content": "name: Feature Tests - Proxy Support\non:\n  push:\n    branches: [ 'disabled']\n#  workflow_dispatch:\n#    inputs:\n#     branch:\n#        description: 'Branch to run the workflow on'\n#        required: true\n#        default: 'main'\n#      run_id:\n#        description: 'Run ID of the producing workflow'\n#        required: true\n#        default: '0'\n#      sha:\n#        description: 'SHA of the commit to run the workflow on'\n#        required: true\n#        default: ''\n#      called_by_workflow:\n#        description: 'Name of the producing workflow'\n#        required: true\n#        default: 'manual-trigger'\n#      full_ref:\n#        description: 'Full git ref of the commit to run the workflow on'\n#        required: false\n#        default: ''\n\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WES_HOST: ${{ secrets.WES_HOST }}\n\n      - name: \"test dns resolution\"\n        run: |\n          host $WES_HOST 127.0.0.1\n        env:\n          WES_HOST: ${{ secrets.WES_HOST }}\n\n      - name: \"proxy container\"\n        run: |\n          docker pull mosajjal/pproxy:latest\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"acme-sh.acme\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\", \\\"acme-sh.\\$\\\": \\\"http\\:\\/\\/proxy.acme:8080\\\"}/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Openssl - Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Openssl - Test if https://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl --insecure -f https://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Openssl - Enroll acme.sh - http challenge validation\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme -d acme-sh. --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker logs proxy | grep http | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Openssl - Enroll acme.sh - alpn challenge validation\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --alpn -d acme-sh. --alpn --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile examples/Docker/data/acme_ca/root-ca-cert.pem -untrusted examples/Docker/data/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker logs proxy | grep http | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Setup certifier ca_handler for proxy usage\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_host: $NCM_API_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"ncm.nclm.eu\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          NCM_API_HOST: ${{ secrets.NCM_API_HOST }}\n          NCM_API_USER: ${{ secrets.NCM_API_USER }}\n          NCM_API_PASSWORD: ${{ secrets.NCM_API_PASSWORD }}\n          NCM_CA_NAME: ${{ secrets.NCM_CA_NAME }}\n          NCM_CA_BUNDLE: ${{ secrets.NCM_CA_BUNDLE }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"Certifier - Enroll via certifier ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Certifier - Revoke via certifier ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --revoke -d acme-sh.acme --standalone --debug 3 --output-insecure\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Setup using http-basic-auth for proxy usage\"\n        run: |\n          sudo mkdir -p examples/Docker/data/est\n          sudo chmod -R 777 examples/Docker/data/est\n          sudo touch $HOME/.rnd\n          sudo openssl ecparam -genkey -name prime256v1 -out examples/Docker/data/est/est_client_key.pem\n          sudo openssl req -new -key examples/Docker/data/est/est_client_key.pem -out /tmp/request.p10 -subj '/CN=acme2certifier'\n          sudo curl http://testrfc7030.com/dstcax3.pem --output /tmp/dstcax3.pem\n          sudo curl https://testrfc7030.com:8443/.well-known/est/cacerts -o /tmp/cacerts.p7 --cacert /tmp/dstcax3.pem\n          sudo openssl base64 -d -in /tmp/cacerts.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out examples/Docker/data/est/ca_bundle.pem\n          sudo curl https://testrfc7030.com:8443/.well-known/est/simpleenroll --anyauth -u estuser:estpwd -s -o /tmp/cert.p7 --cacert /tmp/dstcax3.pem --data-binary @/tmp/request.p10 -H \"Content-Type: application/pkcs10\" --dump-header /tmp/resp.hdr\n          sudo openssl base64 -d -in /tmp/cert.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out examples/Docker/data/est/est_client_cert.pem\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/est_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_host: https://testrfc7030.com:8443\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_user: estuser\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"est_password: estpwd\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"testrfc7030.com\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"EST - Enroll via EST using http-basic-auth\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      #- name: \"setup nclm ca_handler for proxy usage\"\n      #  run: |\n      #    sudo cp examples/ca_handler/nclm_ca_handler.py examples/Docker/data/ca_handler.py\n      #    sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n      #    sudo chmod 777 examples/Docker/data/acme_srv.cfg\n      #    sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"api_host: ${{ secrets.NCLM_API_HOST }}\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"api_user: ${{ secrets.NCLM_API_USER }}\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"api_password: ${{ secrets.NCLM_API_PASSWORD }}\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"tsg_name: ${{ secrets.NCLM_TSG_NAME }}\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"ca_name: ${{ secrets.NCLM_CA_NAME }}\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo echo \"ca_id_list: [${{ secrets.NCLM_CA_ID_LIST }}]\" >> examples/Docker/data/acme_srv.cfg\n      #    sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"nclm.eu\\$\\\": \\\"http:\\/\\/proxy.acme:8080\\\"}/g\" examples/Docker/data/acme_srv.cfg\n      #    cd examples/Docker/\n      #    docker compose restart\n      #    docker compose logs\n\n      #- name: \"Sleep for 5s\"\n      #  uses: juliangruber/sleep-action@v2.0.3\n      #  with:\n      #    time: 5s\n\n      #- name: \"Enroll via nclm ca_handler\"\n      #  run: |\n      #    docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n      #    # openssl verify -CAfile acme.sh/acme-sh.acme/ca.cer acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      #- name: \"Check proxy logs\"\n      #  run: |\n      #    docker logs proxy | grep http | grep -- \"->\"\n      #    docker stop proxy\n      #    docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Setup msca ca_handler for proxy usage\"\n        run: |\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo cp test/ca/certsrv_ca_certs.pem examples/Docker/data/ca_certs.pem\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > examples/Docker/data/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/mscertsrv_ca_handler.py\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"host: $WES_HOST\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"user: $WES_USER\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"password: $WES_PASSWORD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"auth_method: $WES_AUTHMETHOD\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"template: $WES_TEMPLATE\" >> examples/Docker/data/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/ca_certs.pem\" >> examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"amazonaws.com\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" examples/Docker/data/acme_srv.cfg\n          cd examples/Docker/\n          docker compose restart\n        env:\n          WES_HOST: ${{ secrets.WES_HOST }}\n          WES_USER: ${{ secrets.WES_USER }}\n          WES_PASSWORD: ${{ secrets.WES_PASSWORD }}\n          WES_AUTHMETHOD: ${{ secrets.WES_AUTHMETHOD }}\n          WES_TEMPLATE: ${{ secrets.WES_TEMPLATE }}\n\n      - name: \"Prepare ssh environment on ramdisk \"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ secrets.WCCE_SSH_ACCESS_KEY }}\n          KNOWN_HOSTS: ${{ secrets.WCCE_SSH_KNOWN_HOSTS }}\n\n      - name: \"Setup ssh forwarder\"\n        run: |\n            docker run -d --rm --network acme --name=$WCCE_FQDN_WOTLD  -e \"MAPPINGS=445:$WCCE_HOST:445; 443:$WCCE_HOST:443; 88:$WCCE_HOST:88\" -e \"SSH_HOST=$SSH_HOST\" -e \"SSH_PORT=$SSH_PORT\" -e \"SSH_USER=$SSH_USER\" -p 443:443 -p 445:445 -p 88:88 -v \"/tmp/rd/ak.tmp:/ssh_key:ro\" davidlor/ssh-port-forward-client:dev\n        env:\n          SSH_USER: ${{ secrets.WCCE_SSH_USER }}\n          SSH_HOST: ${{ secrets.WCCE_SSH_HOST }}\n          SSH_PORT: ${{ secrets.WCCE_SSH_PORT }}\n          WCCE_HOST: ${{ secrets.WCCE_HOST }}\n          WCCE_FQDN_WOTLD: ${{ secrets.WCCE_FQDN_WOTLD }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"MScertsrv - Enroll via msca ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n          # openssl verify -CAfile acme.sh/acme-sh.acme/ca.cer acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Stop proxy container\"\n        run: |\n          docker stop proxy\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"get runner ip\"\n        run: |\n          echo RUNNER_IP=$(ip addr show eth0 | grep -i \"inet \" | cut -d ' ' -f 6 | cut -d '/' -f 1) >> $GITHUB_ENV\n          echo RUNNER_PATH=$(pwd | sed 's_/_\\\\/_g') >> $GITHUB_ENV\n\n      - run: echo \"runner IP is $RUNNER_IP\"\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Install dnsmasq\"\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y dnsmasq\n          sudo systemctl disable systemd-resolved\n          sudo systemctl stop systemd-resolved\n          sudo mkdir -p dnsmasq\n          sudo cp .github/dnsmasq.conf dnsmasq/\n          sudo chmod -R 777 dnsmasq/dnsmasq.conf\n          sudo sed -i \"s/RUNNER_IP/$RUNNER_IP/g\" dnsmasq/dnsmasq.conf\n          sudo echo \"address=/$WES_HOST/$RUNNER_IP\" >> dnsmasq/dnsmasq.conf\n          cat dnsmasq/dnsmasq.conf\n          sudo cp dnsmasq/dnsmasq.conf /etc/\n          sudo systemctl enable dnsmasq\n          sudo systemctl start dnsmasq\n        env:\n          RUNNER_IP: ${{ env.RUNNER_IP }}\n          WES_HOST: ${{ secrets.WES_HOST }}\n\n      - name: \"test dns resolution\"\n        run: |\n          host $WES_HOST 127.0.0.1\n        env:\n          WES_HOST: ${{ secrets.WES_HOST }}\n\n      - name: \"proxy container\"\n        run: |\n          docker pull mosajjal/pproxy:latest\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: Retrieve proxy-ip\n        run: |\n          echo PROXY_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' proxy) >> $GITHUB_ENV\n      - run: echo \"Latest tag is ${{ env.PROXY_IP }}\"\n\n      - name: \"setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"acme-sh.acme\\$\\\": \\\"socks5:\\/\\/${{ env.PROXY_IP }}:8080\\\", \\\"acme-sh.\\$\\\": \\\"http\\:\\/\\/${{ env.PROXY_IP }}:8080\\\"}/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test if http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Prepare acme.sh container\"\n        run: |\n          docker run --rm -id -v \"$(pwd)/acme-sh\":/acme.sh --network acme --name=acme-sh neilpang/acme.sh:latest daemon\n\n      - name: \"Enroll acme.sh - http challenge validation\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme -d acme-sh. --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker logs proxy | grep http | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Enroll acme.sh - alpn challenge validation\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --alpn -d acme-sh. --alpn --standalone --debug 3 --output-insecure --force\n          openssl verify -CAfile data/volume/acme_ca/root-ca-cert.pem -untrusted data/volume/acme_ca/sub-ca-cert.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker logs proxy | grep http | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Prepare acme_srv.cfg with certifier_ca_handler and proxy usage\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: examples/ca_handler/certifier_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_host: $NCM_API_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_user: $NCM_API_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"api_password: $NCM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_name: $NCM_CA_NAME\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: $NCM_CA_BUNDLE\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"ncm.nclm.eu\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          NCM_API_HOST: ${{ secrets.NCM_API_HOST }}\n          NCM_API_USER: ${{ secrets.NCM_API_USER }}\n          NCM_API_PASSWORD: ${{ secrets.NCM_API_PASSWORD }}\n          NCM_CA_NAME: ${{ secrets.NCM_CA_NAME }}\n          NCM_CA_BUNDLE: ${{ secrets.NCM_CA_BUNDLE }}\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via certifier_ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --issue -d acme-sh.acme --standalone --debug 3 --output-insecure &\n          sleep 45\n          awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > \"cert-\" c \".pem\"}' < acme-sh/acme-sh.acme_ecc/ca.cer\n          openssl verify -CAfile cert-2.pem -untrusted cert-1.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Revoke via certifier ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --revoke -d acme-sh.acme --standalone --debug 3 --output-insecure\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"Setup esthandler using http-basic-auth\"\n        run: |\n          sudo mkdir -p data/acme_ca\n          sudo chmod -R 777 data/acme_ca\n          sudo touch $HOME/.rnd\n          sudo openssl ecparam -genkey -name prime256v1 -out data/acme_ca/est_client_key.pem\n          sudo chmod a+rx data/acme_ca/est_client_key.pem\n          sudo openssl req -new -key data/acme_ca/est_client_key.pem -out /tmp/request.p10 -subj '/CN=acme2certifier'\n          sudo curl http://testrfc7030.com/dstcax3.pem --output /tmp/dstcax3.pem\n          sudo curl https://testrfc7030.com:8443/.well-known/est/cacerts -o /tmp/cacerts.p7 --cacert /tmp/dstcax3.pem\n          sudo openssl base64 -d -in /tmp/cacerts.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out data/volume/acme_ca/ca_bundle.pem\n          sudo curl https://testrfc7030.com:8443/.well-known/est/simpleenroll --anyauth -u estuser:estpwd -s -o /tmp/cert.p7 --cacert /tmp/dstcax3.pem --data-binary @/tmp/request.p10 -H \"Content-Type: application/pkcs10\" --dump-header /tmp/resp.hdr\n          sudo openssl base64 -d -in /tmp/cert.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out data/volume/acme_ca/est_client_cert.pem\n          sudo cp .github/openssl_ca_handler.py_acme_srv_default_handler.cfg data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/est_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_host: https://testrfc7030.com:8443\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_user: estuser\" >> data/volume/acme_srv.cfg\n          sudo echo \"est_password: estpwd\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: False\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"testrfc7030.com\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" data/volume/acme_srv.cfg\n\n      - name: \"Reconfigure a2c \"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT restart\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via EST using http-basic-auth\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      #- name: \"Prepare acme_srv.cfg with nclm_ca_handler\"\n      #  run: |\n      #    mkdir -p data/volume/acme_ca\n      #    sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n      #    sudo touch data/volume/acme_srv.cfg\n      #    sudo chmod 777 data/volume/acme_srv.cfg\n      #    sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n      #    sudo echo \"handler_file: examples/ca_handler/nclm_ca_handler.py\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"api_host: $NCLM_API_HOST\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"api_user: $NCLM_API_USER\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"api_password: $NCLM_API_PASSWORD\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"tsg_name: $NCLM_TSG_NAME\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"ca_name: $NCLM_CA_NAME\" >> data/volume/acme_srv.cfg\n      #    sudo echo \"ca_id_list: [$NCLM_CA_ID_LIST]\" >> data/volume/acme_srv.cfg\n      #    sudo sed -i \"s/revocation_reason_check_disable: False/revocation_reason_check_disable: False\\nenrollment_timeout: 30/g\" data/volume/acme_srv.cfg\n      #    sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"nclm.eu\\$\\\": \\\"http:\\/\\/proxy.acme:8080\\\"}/g\" data/acme_srv.cfg\n      #  env:\n      #    NCLM_API_HOST: ${{ secrets.NCLM_API_HOST }}\n      #    NCLM_API_USER: ${{ secrets.NCLM_API_USER }}\n      #    NCLM_API_PASSWORD: ${{ secrets.NCLM_API_PASSWORD }}\n      #    NCLM_TSG_NAME: ${{ secrets.NCLM_TSG_NAME }}\n      #    NCLM_CA_NAME: ${{ secrets.NCLM_CA_NAME }}\n      #    NCLM_CA_ID_LIST: ${{ secrets.NCLM_CA_ID_LIST }}\n\n      #- name: \"[ PREPARE  ] reconfigure a2c \"\n      #  run: |\n      #    docker exec acme-srv sh /tmp/acme2certifier/rpm_tester.sh restart\n\n      #- name: \"Enroll via nclm_ca_handler\"\n      #  run: |\n      #    docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force &\n      #    docker stop proxy\n      #    docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      #- name: \"Check proxy logs\"\n      #  run: |\n      #    docker logs proxy | grep socks5 | grep -- \"->\"\n\n      - name: \"ssh environment on ramdisk\"\n        run: |\n          sudo mkdir -p /tmp/rd\n          sudo mount -t tmpfs -o size=5M none /tmp/rd\n          sudo echo \"$SSH_KEY\" > /tmp/rd/ak.tmp\n          sudo chmod 600 /tmp/rd/ak.tmp\n          sudo echo \"$KNOWN_HOSTS\" > /tmp/rd/known_hosts\n        env:\n          SSH_KEY: ${{ secrets.WCCE_SSH_ACCESS_KEY }}\n          KNOWN_HOSTS: ${{ secrets.WCCE_SSH_KNOWN_HOSTS }}\n\n      - name: \"establish SSH connection\"\n        run: sudo ssh $SSH_USER@$SSH_HOST -i /tmp/rd/ak.tmp -p $SSH_PORT -o UserKnownHostsFile=/tmp/rd/known_hosts -L 443:$WES_IP:443 -g ping -c 180 $WES_IP &\n        env:\n          SSH_USER: ${{ secrets.CMP_SSH_USER }}\n          SSH_HOST: ${{ secrets.CMP_SSH_HOST }}\n          SSH_PORT: ${{ secrets.CMP_SSH_PORT }}\n          WES_IP: ${{ secrets.WES_IP }}\n\n      - name: \"Sleep for 5s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 5s\n\n      - name: \"setup msca ca_handler for proxy usage\"\n        run: |\n          mkdir -p data/volume/acme_ca\n          sudo cp test/ca/certsrv_ca_certs.pem data/volume/acme_ca/ca_certs.pem\n          sudo touch data/volume/acme_srv.cfg\n          sudo chmod 777 data/volume/acme_srv.cfg\n          sudo head -n -8 .github/openssl_ca_handler.py_acme_srv_default_handler.cfg > data/volume/acme_srv.cfg\n          sudo echo \"handler_file: /opt/acme2certifier/examples/ca_handler/mscertsrv_ca_handler.py\" >> data/volume/acme_srv.cfg\n          sudo echo \"host: $WES_HOST\" >> data/volume/acme_srv.cfg\n          sudo echo \"user: $WES_USER\" >> data/volume/acme_srv.cfg\n          sudo echo \"password: $WES_PASSWORD\" >> data/volume/acme_srv.cfg\n          sudo echo \"auth_method: $WES_AUTHMETHOD\" >> data/volume/acme_srv.cfg\n          sudo echo \"template: $WES_TEMPLATE\" >> data/volume/acme_srv.cfg\n          sudo echo \"ca_bundle: volume/acme_ca/ca_certs.pem\" >> data/volume/acme_srv.cfg\n          sudo sed -i \"s/debug: True/debug: True\\nproxy_server_list: {\\\"amazonaws.com\\$\\\": \\\"socks5:\\/\\/proxy.acme:8080\\\"}/g\" data/volume/acme_srv.cfg\n        env:\n          WES_HOST: ${{ secrets.WES_HOST }}\n          WES_USER: ${{ secrets.WES_USER }}\n          WES_PASSWORD: ${{ secrets.WES_PASSWORD }}\n          WES_AUTHMETHOD: ${{ secrets.WES_AUTHMETHOD }}\n          WES_TEMPLATE: ${{ secrets.WES_TEMPLATE }}\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Enroll via msca ca_handler\"\n        run: |\n          docker exec -i acme-sh acme.sh --server http://acme-srv --accountemail 'acme-sh@example.com' --issue -d acme-sh.acme --standalone --debug 3 --output-insecure --force &\n          # sleep 45\n          # openssl verify -CAfile data/acme_ca/ca_certs.pem acme-sh/acme-sh.acme_ecc/acme-sh.acme.cer\n\n      - name: \"Check proxy logs\"\n        run: |\n          docker logs proxy | grep socks5 | grep -- \"->\"\n          docker stop proxy\n          docker run -d -it --name=proxy --network acme --rm -p 8080:8080 mosajjal/pproxy:latest -vv &\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          # sudo cp -rp acme-sh/ ${{ github.workspace }}/artifact/acme-sh/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          docker logs proxy > ${{ github.workspace }}/artifact/proxy.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data proxy.log acme-srv.log # acme-sh\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/feature-tnauth.yml",
    "content": "name: Feature Tests - Tnauth\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images (matrix from payload)\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n          NAME_SPACE: acme\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: True/g\" examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Sleep for 10s\"\n        uses: juliangruber/sleep-action@v2.0.3\n        with:\n          time: 10s\n\n      - name: \"Test regular enrollment\"\n        uses: ./.github/actions/acme_clients\n        with:\n          TEST_ADL: \"false\"\n\n      - name: \"Install curl and socat and test connction\"\n        run: |\n          sudo apt-get install -y curl socat\n          curl -f http://localhost:22280\n\n      - name: \"Install acme.sh\"\n        run: |\n          mkdir /tmp/acme_sh\n          curl -kL https://github.com/grindsa/acme.sh/archive/tnauth_list_support.tar.gz | tar xz -C /tmp/acme_sh --strip-components=1\n\n      - name: \"Enroll certificate using tnauth identifier\"\n        run: |\n          cd /tmp/acme_sh\n          /tmp/acme_sh/acme.sh --server http://127.0.0.1:22280 --accountemail grindsa@tnauth.acme --issue -d cert.acme.local --tnauth 123456 --spctoken 1234 --standalone --force --debug 2\n\n      - name: \"Check container configuration\"\n        uses: ./.github/actions/container_check\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-containers-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test RPMs (EL8 & EL9) downloaded from the producer run\n  # ---------------------------------------------------------\n  test-rpm:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        rhversion: [8, 9]\n        execscript: ['rpm_tester.sh', 'django_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Alma environment\"\n        uses: ./.github/actions/rpm_prep\n        with:\n          GH_USER: ${{ env.GH_USER }}\n          GH_SBOM_REPO_TOKEN: ${{ env.GH_SBOM_REPO_TOKEN }}\n          RH_VERSION: ${{ matrix.rhversion }}\n          RPM_BUILD: false\n\n      - name: \"Download RPM artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}.noarch.rpm\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: True/g\" data/volume/acme_srv.cfg\n\n      - name: \"Execute install scipt\"\n        run: |\n          docker exec acme-srv sh /tmp/acme2certifier/$EXEC_SCRIPT\n        env:\n          EXEC_SCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Install curl and socat and test connction\"\n        run: |\n          sudo apt-get install -y curl socat\n          curl -f http://localhost:22280\n\n      - name: \"Install acme.sh\"\n        run: |\n          mkdir /tmp/acme_sh\n          curl -kL https://github.com/grindsa/acme.sh/archive/tnauth_list_support.tar.gz | tar xz -C /tmp/acme_sh --strip-components=1\n\n      - name: \"Enroll certificate using tnauth identifier\"\n        run: |\n          cd /tmp/acme_sh\n          /tmp/acme_sh/acme.sh --server http://127.0.0.1:22280 --accountemail grindsa@tnauth.acme --issue -d cert.acme.local --tnauth 123456 --spctoken 1234 --standalone --force --debug 2\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /opt/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.rpm\n          # sudo cp -rp /tmp/acme_sh/ ${{ github.workspace }}/artifact/acme_sh/\n          docker exec acme-srv cat /etc/nginx/nginx.conf.orig > ${{ github.workspace }}/artifact/data/nginx.conf.orig\n          docker exec acme-srv cat /etc/nginx/nginx.conf > ${{ github.workspace }}/artifact/data/nginx.conf\n          docker exec acme-srv cat /var/log/messages > ${{ github.workspace }}/artifact/acme-srv.log\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-rpm-rh${{ matrix.rhversion }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n\n  # ---------------------------------------------------------\n  # Test DEB downloaded from the producer run\n  # ---------------------------------------------------------\n  test-deb:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        execscript: ['deb_tester.sh']\n\n    steps:\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Parse JSON secret\"\n        uses: ./.github/actions/parse-json-secret\n        with:\n          json_secret: ${{ secrets.GH_CFG }}\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'deb_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n\n      - name: \"Prepare Ubuntu environment\"\n        if: matrix.execscript == 'django_tester.sh'\n        uses: ./.github/actions/deb_prep\n        with:\n          DEB_BUILD: false\n          DJANGO_DB: psql\n\n      - name: \"Download DEB artifact by name from build run\"\n        uses: ./.github/actions/download_artifact\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: acme2certifier-${{ inputs.run_id }}-1_all.deb\n          DESTINATION_PATH: data/\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare acme_srv.cfg with openssl_ca_handler\"\n        run: |\n          mkdir -p data/volume/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem data/volume/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg data/volume/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/tnauthlist_support: True/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/examples\\/ca_handler/\\/var\\/www\\/acme2certifier\\/examples\\/ca_handler/g\" data/volume/acme_srv.cfg\n          sudo sed -i \"s/volume\\/acme_ca/\\/var\\/www\\/acme2certifier\\/volume\\/acme_ca/g\"  data/volume/acme_srv.cfg\n\n      - name: \"Execute install script\"\n        run: |\n          docker exec acme-srv bash /tmp/acme2certifier/$EXECSCRIPT install $WEBSRV\n        env:\n          WEBSRV: ${{ matrix.websrv }}\n          EXECSCRIPT: ${{ matrix.execscript }}\n\n      - name: \"Test http://acme-srv/directory is accessible\"\n        run: docker run -i --rm --network acme curlimages/curl -f http://acme-srv/directory\n\n      - name: \"Install curl and socat and test connction\"\n        run: |\n          sudo apt-get install -y curl socat\n          curl -f http://localhost:22280\n\n      - name: \"Install acme.sh\"\n        run: |\n          mkdir /tmp/acme_sh\n          curl -kL https://github.com/grindsa/acme.sh/archive/tnauth_list_support.tar.gz | tar xz -C /tmp/acme_sh --strip-components=1\n\n      - name: \"Enroll certificate using tnauth identifier\"\n        run: |\n          cd /tmp/acme_sh\n          /tmp/acme_sh/acme.sh --server http://127.0.0.1:22280 --accountemail grindsa@tnauth.acme --issue -d cert.acme.local --tnauth 123456 --spctoken 1234 --standalone --force --debug 2\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          docker exec acme-srv tar cvfz /tmp/acme2certifier/a2c.tgz /var/www/acme2certifier\n          sudo cp -rp data/ ${{ github.workspace }}/artifact/data/\n          sudo rm ${{ github.workspace }}/artifact/data/*.deb\n          if [ ${{ matrix.websrv }} == \"apache2\" ]; then\n            docker exec acme-srv cat /var/log/apache2/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          else\n            docker exec acme-srv cat /var/log/nginx/error.log > ${{ github.workspace }}/artifact/acme-srv.log\n          fi\n          docker exec acme-srv cat /var/log/syslog > ${{ github.workspace }}/artifact/syslog\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz data acme-srv.log syslog\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: test-deb-${{ matrix.websrv }}-${{ matrix.execscript }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/\n"
  },
  {
    "path": ".github/workflows/helper-dump-secrets.yml",
    "content": "name: Dump Secrets to JSON\n\non:\n  # push:\n  workflow_dispatch:\n    inputs:\n      secret_list:\n        description: 'Comma-separated list of secret names to dump'\n        required: true\n        default: 'ASA_API_USER,ASA_API_PASSWORD,ASA_API_KEY,ASA_API_HOST,ASA_CA_BUNDLE,ASA_CA_NAME,ASA_CA_NAME2,ASA_PROFILE1,ASA_PROFILE2,ASA_PROFILE3'\n      output_filename:\n        description: 'Output JSON filename'\n        required: false\n        default: 'secrets-dump.json'\n      mask_values:\n        description: 'Mask secret values in logs'\n        required: false\n        default: 'true'\n        type: choice\n        options:\n        - 'true'\n        - 'false'\n\njobs:\n  dump-secrets:\n    name: Dump Secrets to JSON\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      - name: Dump secrets to JSON\n        id: dump-secrets\n        uses: ./.github/actions/dump-secrets-to-json\n        with:\n          # secret_names: ${{ github.event.inputs.secret_list }}\n          secret_names: 'ASA_API_USER,ASA_API_PASSWORD,ASA_API_KEY,ASA_API_HOST,ASA_CA_BUNDLE,ASA_CA_NAME,ASA_CA_NAME2,ASA_PROFILE1,ASA_PROFILE2,ASA_PROFILE3'\n          output_file: 'secrets-dump.json'\n          mask_values: True\n          #output_file: ${{ github.event.inputs.output_filename }}\n          #mask_values: ${{ github.event.inputs.mask_values }}\n        env:\n          # API Secrets\n          ASA_API_USER: ${{ secrets.ASA_API_USER }}\n          ASA_API_PWD: ${{ secrets.ASA_API_PWD }}\n          ASA_API_HOST: ${{ secrets.ASA_API_HOST }}\n          ASA_API_PORT: ${{ secrets.ASA_API_PORT }}\n          ASA_CA_BUNDLE: ${{ secrets.ASA_CA_BUNDLE }}\n          ASA_CA_NAME: ${{ secrets.ASA_CA_NAME }}\n          ASA_CA_NAME2: ${{ secrets.ASA_CA_NAME2 }}\n          ASA_PROFILE1: ${{ secrets.ASA_PROFILE1 }}\n          ASA_PROFILE2: ${{ secrets.ASA_PROFILE2 }}\n          ASA_PROFILE3: ${{ secrets.ASA_PROFILE3 }}\n\n      - name: Show dump results\n        run: |\n          echo \"✅ JSON file created: ${{ steps.dump-secrets.outputs.json_file }}\"\n          echo \"📊 Secrets processed: ${{ steps.dump-secrets.outputs.secret_count }}\"\n          echo \"📁 File size: $(wc -c < '${{ steps.dump-secrets.outputs.json_file }}') bytes\"\n\n          # Show file structure (without content for security)\n          echo \"📋 JSON structure:\"\n          if command -v jq >/dev/null 2>&1; then\n            jq -r 'keys[]' \"${{ steps.dump-secrets.outputs.json_file }}\" | head -10\n            echo \"...\"\n          else\n            echo \"jq not available, skipping structure preview\"\n          fi\n\n      - name: Upload JSON as artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: secrets-dump-${{ github.run_number }}\n          path: ${{ steps.dump-secrets.outputs.json_file }}\n          retention-days: 1\n        if: always()\n\n      - name: Create summary\n        run: |\n          echo \"## 🔐 Secrets Dump Summary\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          echo \"| Metric | Value |\" >> $GITHUB_STEP_SUMMARY\n          echo \"|--------|--------|\" >> $GITHUB_STEP_SUMMARY\n          echo \"| 📄 Output File | \\`${{ steps.dump-secrets.outputs.json_file }}\\` |\" >> $GITHUB_STEP_SUMMARY\n          echo \"| 🔢 Secrets Processed | ${{ steps.dump-secrets.outputs.secret_count }} |\" >> $GITHUB_STEP_SUMMARY\n          echo \"| 💾 File Size | $(wc -c < '${{ steps.dump-secrets.outputs.json_file }}') bytes |\" >> $GITHUB_STEP_SUMMARY\n          echo \"| 🕐 Generated | $(date -u +'%Y-%m-%d %H:%M:%S UTC') |\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          echo \"### 📦 Artifact\" >> $GITHUB_STEP_SUMMARY\n          echo \"The JSON file has been uploaded as an artifact: \\`secrets-dump-${{ github.run_number }}\\`\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          echo \"### ⚠️ Security Notice\" >> $GITHUB_STEP_SUMMARY\n          echo \"The uploaded artifact contains sensitive information. Handle with care and delete after use.\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/main-build.yml",
    "content": "name: build\non:\n\n  push:\n    branches: [ \"**\" ]       # build on any branch\n  workflow_dispatch: {}\n  schedule:\n    - cron: '0 02 * * 6'  # Every Saturday at 4:00am\n\n\njobs:\n  # ---------------------------------------------------------\n  # Job 1: Build four container images (matrix) and push to GHCR\n  # ---------------------------------------------------------\n  build-containers:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: write\n    if: github.repository == 'grindsa/acme2certifier' && !startsWith(github.ref_name, 'wf_')\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n      - uses: actions/checkout@v6\n      - name: \"Build container\"\n        uses: ./.github/actions/container_build_upload\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n  # ---------------------------------------------------------\n  # Job 2: Build RPM and upload artifacts\n  # ---------------------------------------------------------\n  build-rpm:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: write\n    if: github.repository == 'grindsa/acme2certifier' && !startsWith(github.ref_name, 'wf_')\n    steps:\n      - uses: actions/checkout@v6\n      - name: \"Build rpm package\"\n        id: rpm_build\n        uses: ./.github/actions/rpm_build_upload\n\n\n  # ---------------------------------------------------------\n  # Job 3: Build one DEB and upload artifact\n  # ---------------------------------------------------------\n  build-deb:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: write\n    if: github.repository == 'grindsa/acme2certifier' && !startsWith(github.ref_name, 'wf_')\n    steps:\n      - uses: actions/checkout@v6\n      - name: \"deb build and upload\"\n        uses: ./.github/actions/deb_build_upload\n        with:\n          NO_VERSION: \"true\"\n\n  # ---------------------------------------------------------\n  # Dispatch tests with full payload (branch/ref-safe)\n  # ---------------------------------------------------------\n  dispatch:\n    runs-on: ubuntu-latest\n    needs: [ build-containers, build-rpm, build-deb ]\n    permissions:\n      contents: read\n      actions: read\n      deployments: write\n    if: github.repository == 'grindsa/acme2certifier' && !startsWith(github.ref_name, 'wf_')\n    steps:\n      - name: Dispatch repository event\n        uses: peter-evans/repository-dispatch@v4\n        with:\n          token: ${{ secrets.GH_WF_TOKEN }}\n          event-type: artifacts_ready\n          client-payload: >-\n            {\n              \"sha\":        \"${{ github.sha }}\",\n              \"branch\":     \"${{ github.ref_name }}\",\n              \"full_ref\":   \"${{ github.ref }}\",\n              \"run_id\":     \"${{ github.run_id }}\",\n              \"called_by_workflow\": \"${{ github.workflow }}\"\n            }\n"
  },
  {
    "path": ".github/workflows/main-create-release.yml",
    "content": "on:\n  push:\n    branches:\n      - \"master\"\n\nname: Create Release\n\njobs:\n  build:\n    name: Create Release\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n\n    steps:\n\n      - name: \"Get current version\"\n        uses: oprypin/find-latest-tag@v1\n        with:\n          repository: ${{ github.repository }}  # The repository to scan.\n          releases-only: true  # We know that all relevant tags have a GitHub release for them.\n        id: acme2certifier_ver  # The step ID to refer to later.\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Retrieve Version from version.py\n        run: |\n          echo APP_NAME=$(echo \"$GITHUB_REPOSITORY\" | awk -F / '{print $2}') >> $GITHUB_ENV\n          echo TAG_NAME=$(cat acme_srv/version.py | grep -i __version__ | head -n 1 | sed 's/__version__ = //g' | sed s/\\\"//g) >> $GITHUB_ENV\n        env:\n          GITHUB_REPOSITORY: ${{ github.repository }}\n\n      - name: \"Display version information\"\n        run: |\n          echo \"Repo is at version $ACME2CERTIFIER_VERSION\"\n          echo \"APP tag is $APP_NAME\"\n          echo \"Latest tag is $TAG_NAME\"\n        env:\n          ACME2CERTIFIER_VERSION: ${{ steps.acme2certifier_ver.outputs.tag }}\n          APP_NAME: ${{ env.APP_NAME }}\n          TAG_NAME: ${{ env.TAG_NAME }}\n\n      - name: Retrieve dbversion numbers from version.py and fixture.xml\n        run: |\n          echo DB_VERSION=$(cat acme_srv/version.py | grep -i __dbversion__ | head -n 1 | sed 's/__dbversion__ = //g' | sed s/\\'//g) >> $GITHUB_ENV\n          echo FIXTURE_VERSION=$(awk '/name: dbversion/{getline; print $2}' examples/django/acme_srv/fixture/status.yaml | tr -d \"'\") >> $GITHUB_ENV\n\n      - run: echo \"db verion is ${{ env.DB_VERSION }}\"\n      - run: echo \"fixture version is ${{ env.FIXTURE_VERSION }}\"\n\n      - name: Check fixture version\n        if: env.DB_VERSION != env.FIXTURE_VERSION\n        run: |\n          echo \"Fixture version is not equal to db version\"\n          exit 1\n\n      - name: Create Release\n        id: create_release\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token\n        with:\n          tag_name: ${{ env.TAG_NAME }}\n          release_name: ${{ env.APP_NAME }} ${{ env.TAG_NAME }}\n          body: |\n            [Changelog](https://github.com/grindsa/acme2certifier/blob/master/CHANGES.md)\n          draft: false\n          prerelease: false\n\n      - name: update version number in spec file\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        run: |\n          sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" examples/install_scripts/rpm/acme2certifier.spec\n          sudo sed -i \"s/\\/var\\/www\\/acme2certifier\\/volume/\\/etc\\/nginx/g\" examples/nginx/nginx_acme_srv_ssl.conf\n          git config --global user.email \"grindelsack@gmail.com\"\n          git config --global user.name \"rpm update\"\n          git add examples/nginx\n          git commit -a -m \"rpm update\"\n\n      - name: build RPM package\n        id: rpm_build\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        uses: grindsa/rpmbuild@alma9\n        with:\n          spec_file: \"examples/install_scripts/rpm/acme2certifier.spec\"\n\n      - name: Upload Release Source-RPM\n        id: upload-srpm\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        uses: actions/upload-release-asset@v1\n        env:\n            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n            upload_url: ${{ steps.create_release.outputs.upload_url }}\n            asset_path: ${{ steps.rpm_build.outputs.source_rpm_path }}\n            asset_name: ${{ steps.rpm_build.outputs.source_rpm_name }}\n            asset_content_type: ${{ steps.rpm_build.outputs.rpm_content_type }}\n\n      - name: Upload Release RPM\n        id: upload-rpm\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        uses: actions/upload-release-asset@v1\n        env:\n            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n            upload_url: ${{ steps.create_release.outputs.upload_url }}\n            asset_path: ${{ steps.rpm_build.outputs.rpm_dir_path }}noarch/acme2certifier-${{ env.TAG_NAME }}-1.0.noarch.rpm\n            asset_name: acme2certifier-${{ env.TAG_NAME }}-1.0.noarch.rpm\n            asset_content_type: ${{ steps.rpm_build.outputs.rpm_content_type }}\n\n      - name: Prepare deb packaging environment\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        run: |\n          sudo apt-get -y install build-essential fakeroot dpkg-dev devscripts debhelper\n          rm setup.py\n          cp -R examples/install_scripts/debian ./\n          sudo sed -i \"s/__version__/${{ env.TAG_NAME }}/g\" debian/changelog\n          cd ../\n          tar cvfz ../acme2certifier_${{ env.TAG_NAME }}.orig.tar.gz ./\n\n      - name: \"[ BUILD ] build debian package\"\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        run: |\n          dpkg-buildpackage -uc -us\n          # dpkg -c ../acme2certifier_${{ env.TAG_NAME }}-1_all.deb\n          cp ../acme2certifier_${{ env.TAG_NAME }}-1_all.deb \"$(pwd)/acme2certifier_${{ env.TAG_NAME }}-1_all.deb\"\n          ls -la\n\n      - name: Upload Release deb\n        id: upload-deb\n        if: steps.acme2certifier_ver.outputs.tag != env.TAG_NAME\n        uses: actions/upload-release-asset@v1\n        env:\n            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n            upload_url: ${{ steps.create_release.outputs.upload_url }}\n            asset_path: acme2certifier_${{ env.TAG_NAME }}-1_all.deb\n            asset_name: acme2certifier_${{ env.TAG_NAME }}-1_all.deb\n            asset_content_type: application/vnd.debian.binary-package\n"
  },
  {
    "path": ".github/workflows/main-dispatch-broker.yml",
    "content": "\nname: dispatch-broker\non:\n  repository_dispatch:\n    types: [ artifacts_ready ]\n\njobs:\n  guard:\n    runs-on: ubuntu-latest\n    if: >\n      ${{\n        github.event.client_payload.sha != '' &&\n        github.event.client_payload.run_id != '' &&\n        github.event.client_payload.branch != ''\n      }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    ${{ github.event.client_payload.sha }}\"\n          echo \"From branch:         ${{ github.event.client_payload.branch }}\"\n          echo \"Producer run_id:     ${{ github.event.client_payload.run_id }}\"\n          echo \"Called by workflow:  ${{ github.event.client_payload.called_by_workflow }}\"\n\n  dispatch-broker:\n    runs-on: ubuntu-latest\n    needs: guard\n    if: ${{ !startsWith(github.event.client_payload.branch, 'wf_') }}\n    steps:\n      - name: dispatch-broker\n        uses: grindsa/dispatch-broker-action@v1\n        with:\n          token: ${{ secrets.GH_WF_TOKEN }}\n          repository: ${{ github.repository }}\n          ref: ${{ github.event.client_payload.branch }}\n          client_payload: ${{ toJson(github.event.client_payload) }}\n          exclude_workflows: \"main-build.yml, main-dispatch-broker.yml\"\n"
  },
  {
    "path": ".github/workflows/quality-codescanner.yml",
    "content": "name: Code quality - Code Scanner\non:\n  push:\n    branches:\n      - 'master'\n      - 'devel'\n      - 'min-devel'\n      - 'min'\n\njobs:\n\n  bandit:\n    permissions:\n      contents: read # for actions/checkout to fetch code\n      security-events: write # for github/codeql-action/upload-sarif to upload SARIF results\n      actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status\n\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - uses: actions/checkout@v6\n      - name: Bandit Scan\n        uses: shundor/python-bandit-scan@ab1d87dfccc5a0ffab88be3aaac6ffe35c10d6cd\n        with:\n          # optional arguments\n          # exit with 0, even with results found\n          exit_zero: true # optional, default is DEFAULT\n          # Github token of the repository (automatically created by Github)\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information.\n          # File or directory to run bandit on\n          # path: # optional, default is .\n          # Report only issues of a given severity level or higher. Can be LOW, MEDIUM or HIGH. Default is UNDEFINED (everything)\n          # level: # optional, default is UNDEFINED\n          # Report only issues of a given confidence level or higher. Can be LOW, MEDIUM or HIGH. Default is UNDEFINED (everything)\n          # confidence: # optional, default is UNDEFINED\n          # comma-separated list of paths (glob patterns supported) to exclude from scan (note that these are in addition to the excluded paths provided in the config file) (default: .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)\n          # excluded_paths: # optional, default is DEFAULT\n          # comma-separated list of test IDs to skip\n          # skips: # optional, default is DEFAULT\n          # path to a .bandit file that supplies command line arguments\n          # ini_path: # optional, default is DEFAULT\n\n  codecov:\n    name: Codecov Analysis\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Python\n        uses: actions/setup-python@master\n        with:\n          python-version: 3.9\n\n      - name: Install components\n        run: |\n          sudo apt-get update\n          sudo DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n          libkrb5-dev \\\n          python3-gssapi \\\n\n      - name: Generate coverage report\n        run: |\n          python -m pip install --upgrade pip\n          pip install lxml beautifulsoup4 html5lib\n          pip install pytest\n          pip install pytest-cov impacket\n          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n          cp examples/ca_handler/skeleton_ca_handler.py acme_srv/ca_handler.py\n          cp examples/acme_srv.cfg acme_srv/\n          pytest --cov=./ --cov-report=xml\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v3\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          file: ./coverage.xml\n          flags: unittests\n\n  sonarcloud:\n    name: SonarCloud Analysis\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis\n\n      - name: Setup Python\n        uses: actions/setup-python@v4\n        with:\n          python-version: '3.x'\n\n      - name: Install components\n        run: |\n          sudo apt-get update\n          sudo DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n          libkrb5-dev \\\n          python3-gssapi \\\n\n      - name: Install pytest coverage and any other packages\n        run: |\n          python -m pip install --upgrade pip\n          pip install pytest coverage impacket\n          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n          cp examples/ca_handler/skeleton_ca_handler.py acme_srv/ca_handler.py\n          cp examples/acme_srv.cfg acme_srv/\n\n      - name: run coverage\n        run: |\n          pip install pytest-cov\n          pytest --cov=./ --cov-report=xml\n\n      - name: SonarCloud Scan\n        uses: SonarSource/sonarcloud-github-action@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n\n  codeql:\n    name: CodeQL Analysis\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n      with:\n        # We must fetch at least the immediate parents so that if this is\n        # a pull request then we can checkout the head.\n        fetch-depth: 2\n\n    # If this run was triggered by a pull request event, then checkout\n    # the head of the pull request instead of the merge commit.\n    - run: git checkout HEAD^2\n      if: github.event_name == 'pull_request'\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: python\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v4\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/quality-error.yml",
    "content": "name: Code quality - Error testing\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  # ---------------------------------------------------------\n  # Test container images downloaded from the producer run\n  # ---------------------------------------------------------\n  test-containers:\n    needs: guard\n    runs-on: ubuntu-latest\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      fail-fast: false\n      matrix:\n        websrv: ['apache2', 'nginx']\n        dbhandler: ['wsgi', 'django']\n    steps:\n\n      - name: \"checkout GIT\"\n        uses: actions/checkout@v6\n\n      - name: \"Download and import container\"\n        uses: ./.github/actions/container_load\n        with:\n          RUN_ID: ${{ inputs.run_id }}\n          ARTIFACT_NAME: a2c-${{ inputs.run_id }}.${{ matrix.websrv }}.${{ matrix.dbhandler }}.tar\n          DESTINATION_PATH: /tmp\n          TOKEN: ${{ secrets.GH_WF_TOKEN }}\n          REPO: ${{ github.repository }}\n\n      - name: \"Prepare container environment\"\n        uses: ./.github/actions/container_prep\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n          CONTAINER_BUILD: false\n\n      - name: \"Setup openssl ca_handler\"\n        run: |\n          sudo mkdir -p examples/Docker/data/acme_ca/certs\n          sudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem examples/Docker/data/acme_ca/\n          sudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg examples/Docker/data/acme_srv.cfg\n          sudo chmod 777 examples/Docker/data/acme_srv.cfg\n          sudo sed -i \"s/tnauthlist_support: False/identifier_limit: 2\\nallowed_domainlist: [\\\"*.acme\\\", \\\"*.bar.local\\\"]/g\" examples/Docker/data/acme_srv.cfg\n          echo \"[Directory]\" >> examples/Docker/data/acme_srv.cfg\n          echo \"tos_url: https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf\" >> examples/Docker/data/acme_srv.cfg\n\n      - name: \"Bring up a2c container\"\n        uses: ./.github/actions/container_up\n        with:\n          DB_HANDLER: ${{ matrix.dbhandler }}\n          WEB_SRV: ${{ matrix.websrv }}\n\n      - name: \"Install acmeshell\"\n        uses: ./.github/actions/wf_specific/error_tests/acmeshell_install\n\n      - name: \"Test for Account Resource\"\n        uses: ./.github/actions/wf_specific/error_tests/account_checks\n\n\n      - name: \"Test for Order Resource\"\n        uses: ./.github/actions/wf_specific/error_tests/order_checks\n\n      - name: \"[ * ] Collecting test logs\"\n        if: ${{ failure() }}\n        run: |\n          mkdir -p ${{ github.workspace }}/artifact/upload\n          sudo cp -rp examples/Docker/data/ ${{ github.workspace }}/artifact/data/\n          cd examples/Docker\n          docker compose logs > ${{ github.workspace }}/artifact/docker-compose.log\n          # sudo cp -rp acmeshell/ ${{ github.workspace }}/artifact/acmeshell/\n          sudo tar -C ${{ github.workspace }}/artifact/ -cvzf ${{ github.workspace }}/artifact/upload/artifact.tar.gz docker-compose.log data\n\n      - name: \"[ * ] Uploading artifacts\"\n        uses: actions/upload-artifact@v7\n        if: ${{ failure() }}\n        with:\n          name: container-tests-${{ matrix.websrv }}-${{ matrix.dbhandler }}.tar.gz\n          path: ${{ github.workspace }}/artifact/upload/artifact.tar.gz\n"
  },
  {
    "path": ".github/workflows/quality-markdown.yml",
    "content": "name: Code quality - Markdown Check\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  markdown-link-check:\n    # runs-on: ubuntu-latest\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    steps:\n    - uses: actions/checkout@master\n    - uses: umbrelladocs/action-linkspector@v1\n    - name: Lint changelog file root\n      uses: avto-dev/markdown-lint@v1\n      with:\n        args: '*.md'\n    - name: Lint changelog file root\n      uses: avto-dev/markdown-lint@v1\n      with:\n        args: '*.md'\n    - name: Lint changelog file docs\n      uses: avto-dev/markdown-lint@v1\n      with:\n        args: './docs/*.md'\n    - name: Lint changelog file docker\n      uses: avto-dev/markdown-lint@v1\n      with:\n        args: './examples/Docker/*.md'\n"
  },
  {
    "path": ".github/workflows/quality-python.yml",
    "content": "name: Code quality - Python Tests\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\n\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n  unittest:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      matrix:\n        python_version: ['3.x', '3.12', '3.11', '3.10', '3.9', '3.8']\n\n    name: Python Unittest (${{ matrix.python_version }})\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python_version }}\n      uses: actions/setup-python@v4\n      with:\n        python-version: ${{ matrix.python_version }}\n\n    - name: Install components\n      run: |\n        sudo apt-get update\n        sudo DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n        libkrb5-dev \\\n        python3-gssapi \\\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install pytest impacket\n        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n\n    - name: cp\n      run: |\n         cp examples/ca_handler/skeleton_ca_handler.py acme_srv/ca_handler.py\n         cp examples/db_handler/wsgi_handler.py acme_srv/db_handler.py\n         cp examples/acme_srv.cfg acme_srv/\n    - name: Python test\n      run: |\n        pytest\n\n  pylint:\n    runs-on: ubuntu-latest\n    needs: guard\n    # Prevent workflows from running in forks\n    if: github.repository == 'grindsa/acme2certifier'\n    strategy:\n      matrix:\n        python_version: ['3.x', '3.12', '3.11', '3.10', '3.9', '3.8']\n\n    name: Pylint test (${{ matrix.python_version }})\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python ${{ matrix.python_version }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python_version }}\n\n      - name: Install components\n        run: |\n          sudo apt-get update\n          sudo DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n          libkrb5-dev \\\n          python3-gssapi \\\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install pylint pylint-exit\n          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n\n      - name: cp\n        run: |\n          cp examples/ca_handler/skeleton_ca_handler.py acme_srv/ca_handler.py\n          cp examples/db_handler/wsgi_handler.py acme_srv/db_handler.py\n          cp examples/acme_srv.cfg acme_srv/\n\n      - name: \"Pylint folder: acme\"\n        run: |\n          pylint --rcfile=\".github/pylintrc\" acme_srv/ || pylint-exit $?\n\n      - name: \"Pylint folder: tools\"\n        run: |\n          pylint --rcfile=\".github/pylintrc\" tools/*.py || pylint-exit $?\n\n      - name: \"Pylint folder: examples/db_handler\"\n        run: |\n          pylint --rcfile=\".github/pylintrc\" examples/db_handler/*.py || pylint-exit $?\n\n      - name: \"Pylint folder: examples/ca_handler\"\n        run: |\n          pylint --rcfile=\".github/pylintrc\" examples/ca_handler/*.py || pylint-exit $?\n\n      - name: \"Linting with pycodestyle\"\n        run: |\n          pip install pycodestyle\n          cp .github/pycodestyle ~/.config/pycodestyle\n          pycodestyle --show-source examples/.\n          pycodestyle --show-source acme_srv/.\n          pycodestyle --show-source tools/.\n"
  },
  {
    "path": ".github/workflows/quality-wiki-update.yml",
    "content": "name: Documentation - Wiki Update\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to run the workflow on'\n        required: true\n        default: 'main'\n      run_id:\n        description: 'Run ID of the producing workflow'\n        required: true\n        default: '0'\n      sha:\n        description: 'SHA of the commit to run the workflow on'\n        required: true\n        default: ''\n      called_by_workflow:\n        description: 'Name of the producing workflow'\n        required: true\n        default: 'manual-trigger'\n      full_ref:\n        description: 'Full git ref of the commit to run the workflow on'\n        required: false\n        default: ''\njobs:\n  # ---------------------------------------------------------\n  # Quick guard to validate incoming payload\n  # ---------------------------------------------------------\n  guard:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.sha != '' && inputs.run_id != '' && inputs.branch != '' && github.repository == 'grindsa/acme2certifier' }}\n    steps:\n      - run: |\n          echo \"Triggered by SHA:    $SHA\"\n          echo \"From branch:         $BRANCH\"\n          echo \"Producer run_id:     $RUN_ID\"\n        env:\n          SHA: ${{ inputs.sha }}\n          BRANCH: ${{ inputs.branch }}\n          RUN_ID: ${{ inputs.run_id }}\n\n\n  wiki-update:\n    runs-on: ubuntu-latest\n    # Prevent workflows from running in forks\n    needs: guard\n    if: github.repository == 'grindsa/acme2certifier'  && inputs.branch == 'master'\n    steps:\n      - uses: actions/checkout@v1\n      # Additional steps to generate documentation in \"Documentation\" directory\n      - name: Upload docs to Wiki\n        uses: grindsa/github-wiki-publish-action@customize_wiki_title\n        with:\n          path: \"docs\"\n        env:\n          GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n      - name: Upload Docker to Wiki\n        uses: grindsa/github-wiki-publish-action@customize_wiki_title\n        with:\n          path: \"examples/Docker\"\n        env:\n          GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n# *.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# mystuff\n/acme2certifier/\ndb.sqlite3\n/manage.py\nacme_srv/migrations/\nacme_srv/admin.py\nacme_srv/apps.py\nacme_srv/models.py\nacme_srv/tests.py\nacme_srv/urls.py\nacme_srv/views.py\n# acme_srv/__init__.py\ncommands.txt\n*.bat\n*.db\nacme_srv/*.pem\nacme_srv/cmp/*.pem\nacme_srv/cmp2/*.pem\nacme_srv/cmp/tmp/*.pem\nacme_srv/est-myca/*.pem\nacme_srv/est/*.pem\nacme_srv/msca/*.pem\ntest/ca/foo-ca-crl.pem\n\n*.csr\n*.old\nacme/*.my\n/acme2certifier_wsgi.py\nenv.txt\ngna.py\nacme_srv/db_handler.py\nacme_srv/ca_handler.py\nacme_srv/certifier_ca_handler.py\nacme_srv/acme_srv.cfg\nacme_srv/wsgi_handler.py\nacme_srv/django_handler.py\nacme_srv/a2c_response.py\nacme_srv/ca/*\nacme_srv/xca/*\nacme_srv/openxpki/*\nacme_srv/acme/*\nacme_srv/ejbca/*\nacme_srv/ssl/*\nacme_srv/cn_dump/*\nacme_srv/fixture\nacme_srv/entrust/*\nacme_srv/vault/*\n*.dll\nacme_srv/cmp/WindowsCMPOpenSSL/openssl.exe\nacme_srv/migrations.old\n# Docker data content\nexamples/Docker/data/*\n!examples/Docker/.env\n# acme/\nacme_srv/acme_srv.db.old.*\n# *.cfg\n*.pub\n*.private\n*.key\nsettings.json\ncoverage.lcov\n\n# apple stuff\n.DS_Store\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "﻿# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v3.2.0\n    hooks:\n    -   id: trailing-whitespace\n    -   id: end-of-file-fixer\n    -   id: check-added-large-files\n    -   id: check-case-conflict\n    -   id: check-docstring-first\n    -   id: check-json\n    -   id: check-merge-conflict\n    -   id: check-symlinks\n    -   id: check-toml\n    -   id: check-xml\n    -   id: check-yaml\n        args: [--allow-multiple-documents]\n    -   id: debug-statements\n    # -   id: double-quote-string-fixer\n    -   id: mixed-line-ending\n\n-   repo: https://github.com/psf/black\n    rev: 22.10.0\n    hooks:\n    -   id: black\n\n-   repo: https://github.com/executablebooks/mdformat\n    rev: 0.7.13\n    hooks:\n    - id: mdformat\n      additional_dependencies:\n        - mdformat-black\n        - black\n"
  },
  {
    "path": "CHANGES.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n# Acme2certifier changelog\n\nThis is a high-level summary of the most important changes. For a full list of\nchanges, see the [git commit log](https://github.com/grindsa/acme2certifier/commits)\nand pick the appropriate release branch.\n\n## Changes in 0.42\n\n**Features and Improvements**:\n\n- [Experimental support of MSSQl](https://github.com/grindsa/acme2certifier/blob/devel/docs/external_database_support.md#when-using-sql-server) via django-handler\n- [EAB SQL Handler](https://github.com/grindsa/acme2certifier/blob/devel/docs/eab.md#sql-handler)\n- run allowed_domainlist check as part of order processing\n- Refactoring of Core components completed\n\n## Changes in 0.41.3\n\n**Bug Fixes**:\n\n- [#307 - cert_operations_log option is not taken into account when logging certificate issuance](https://github.com/grindsa/acme2certifier/issues/306)\n\n## Changes in 0.41.2\n\n**Bug Fixes**:\n\n- [#304 - correct parsing of config files with without Challenge section](https://github.com/grindsa/acme2certifier/issues/304)\n- [#302 - fix when loading allowed_domain_list parameter from config](https://github.com/grindsa/acme2certifier/issues/302)\\]\n\n## Changes in 0.41.1\n\n**Bug Fixes**:\n\n- [#299 Improved cert_passphrase_variable handling in EJBCA handler](https://github.com/grindsa/acme2certifier/issues/299)\n\n## Changes in 0.41\n\n- The database schema has been updated. Please ensure you run the appropriate update script after upgrading:\n  - Use `tools/db_update.py` if you are using the `wsgi_handler`\n  - Use `tools/django_update.py` if you are using the `django_handler`\n\n**Features and Improvements**:\n\n- [**Asynchronous Mode**](docs/async_mode.md)\n- **EAB Profiling**:\n  - Support of [domain prevalidation](docs/prevalidated_domainlist.md)\n  - challenge_validation_disable, forward_address_check and reverse_address_check parameters can be configured via[EAB-Profiling feature](docs/eab_profiling.md)\n  - eab_pofiling to be enabled in the `[EABhandler]` section of `acme_srv.cfg`\n- **Challenge Error Reporting**: Challange validation error status will be reported to ACME-client\n- **ACME CA Handla**:\n  - Option to enable periodic synchronization of profiles information from ACME server to be shown as meta-information in Directory ressource\n  - Option to configure renewalinfo endpoint lookup on ACME server to obtain renewal window\n  - Support pre-authorization of domain-names as done by [harica.gr](https://harica.gr)\n\n## Changes in 0.40.1\n\n**Bug Fixes**:\n\n- [#281 - CAhandler' object has no attribute 'profiles'](https://github.com/grindsa/acme2certifier/issues/281)\n\n## Changes in 0.40\n\n**Features and Improvements**:\n\n- **CA Handler**: A CA handler to support [Hashicorp Vault CA](https://developer.hashicorp.com/vault/tutorials/pki/pki-engine)\n- **Order Processing**: [#269](https://github.com/grindsa/acme2certifier/issues/269) Added support of non-compliant order polling via finalize endpoint\n- **EAB (External Account Binding)**: Improved comparison function between inner and outer JWK structures\n- **EAB Profiling**: Added support for revocation operations\n- **DNS Validation**: Added option for DNS reverse zone checking when challenge validation is disabled\n- **Documentation**: Updated mscertsrv_handler documentation to clarify limitations when using GSSAPI authentication\n- **Cryptography Support**: Added support for cryptography module versions > 44.0.0 in mscertsrv_handler.py\n- **Error Messaging**: Enhanced error messages sent to clients when CN/SAN validation checks fail\n- **RPM Packaging**: Minor improvements to RPM service files and RPM spec configuration\n\n**Bug Fixes**:\n\n- [#269](https://github.com/grindsa/acme2certifier/issues/269)\n- Fixed LegacyKeyValueFormat warnings in Dockerfiles\n- **EAB**: Refactored comparison function between inner and outer JWK structures for better reliability\n- **Tools**: Fixed error handling in `tools/django_upgrade.py`\n- **ACME CA Handler**: Improved JWK handling by stripping to minimum required fields\n\n## Changes in 0.39.2\n\n**Bug fixes**:\n\n- [#269](https://github.com/grindsa/acme2certifier/issues/269) allow non-compliant order polling via finalize endpoint\n\n## Changes in 0.39.1\n\n**Bug fixes**:\n\n- [#260](https://github.com/grindsa/acme2certifier/issues/260) improved method for eab key-comparison\n\n## Changes in 0.39\n\n**Upgrade notes**:‚\n\n- The database schema has been updated. Please ensure you run the appropriate update script after upgrading:\n  - Use `tools/db_update.py` if you are using the `wsgi_handler`\n  - Use `tools/django_update.py` if you are using the `django_handler`\n\n**Features and Improvements**:\n\n- **RFC 8823 Support:**\n  Added support for [RFC 8823](https://www.rfc-editor.org/rfc/rfc8823.html) — *Automatic Certificate Management Environment for End-User S/MIME Certificates*. This includes handling of `email` identifiers and the `email-reply-00` challenge type.\n- **Source Address Check:**\n  Introduced the `source_address_check` option, which can be used in combination with `challenge_validation_disable` to verify that the client IP address is registered for the FQDNs included in the order request.\n- **DNS Challenge Support in acme_ca_handler:**\n  Enhanced [acme_ca_handler.py](https://github.com/grindsa/acme2certifier/blob/devel/docs/acme_ca.md) to support DNS challenges.\n- **Certificate Operations Logging:**\n  Added the `cert_operations_log` option to enable logging of certificate issuance and revocation operations.\n\n**Bugfixes**:\n\n- Added documentation for the `contact_check_disable` option.\n- Fixed broken links in the [OpenXPKI documentation](https://github.com/grindsa/acme2certifier/blob/master/docs/openxpki.md).\n- Implemented various logging improvements for better traceability and troubleshooting.\n\n## Changes in 0.38.1\n\n**Bug fixes**:\n\n- [#260](https://github.com/grindsa/acme2certifier/issues/260) improved method for eab key-comparison\n\n## Changes in 0.38\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features and Improvements**:\n\n- Support of [Automated Certificate Management Environment (ACME) Profiles Extension](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/)\n- [#227](https://github.com/grindsa/acme2certifier/issues/227) - Challenge validation can now be disabled using the [EAB profiling feature](docs/eab_profiling.md)\n- [#226](https://github.com/grindsa/acme2certifier/issues/226) - A configuration option has been added to append the Common Name (CN) or the first Subject Alternative Name (SAN) to the eJBCA username.\n- Added support for the [caaIdentities attribute](https://datatracker.ietf.org/doc/html/rfc8555/#section-7.1.1) in the directory object\n\n**Bug fixes**:\n\n- Addressed Bandit warnings related to potential SQL injection vulnerabilities\n- Code formatting improved using [black](https://github.com/psf/black)\n- Markdown linting performed using [mdformat](https://mdformat.readthedocs.io/en/stable/#)\n\n## Changes in 0.37.1\n\n**Bug fixes**:\n\n- [#221](https://github.com/grindsa/acme2certifier/issues/221) - /directory redirection is broken if \"url prefix\" is configured\n\n## Changes in 0.37\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features and Improvements**:\n\n- **EAB Environments Only**:\n  - Implemented a check to prevent certificate enrollment from ACME accounts without EAB credentials. This can be disabled by setting `eabkid_check_disable: True` in `acme_srv.cfg`.\n  - Introduced the `invalid_eabkid_deactivate` option to deactivate ACME accounts lacking EAB credentials.\n- [#213](https://github.com/grindsa/acme2certifier/issues/213) - Added support for multiple CA servers in `mscertsrv_handler`.\n- Introduced the `allowed_domainlist` parameter to filter domain names permitted for enrollment.\n- Developed a prototype `handler_check()` method in `XCA-handler` to reject requests when there is a handler misconfiguration.\n- Added the ability to log enrollment configurations by setting the `enrollment_config_log` parameter.\n- Reviewed and updated multiple documentation files.\n- [#208](https://github.com/grindsa/acme2certifier/pull/209) - Updated OpenXPKI documentation with `authorized_signer` information.\n- [#206](https://github.com/grindsa/acme2certifier/pull/206) - Improved OpenXPKI documentation for enhanced DN handling.\n- [#200](https://github.com/grindsa/acme2certifier/issues/200) - Updated ACME Clients documentation.\n- Disabled logging in Nginx and uWSGI containers.\n\n**Bug Fixes**:\n\n- [#210](https://github.com/grindsa/acme2certifier/issues/210) - Corrected redirection of the root endpoint to the appropriate directory.\n- [#207](https://github.com/grindsa/acme2certifier/pull/207) - Fixed RPC calls in the OpenXPKI CA handler.\n- Refactored allowed_domainlist_check() function to address a potential security issue\n- Enhanced error handling in `xca-handler`.\n- Disabled logging in Nginx and uWSGI containers.\n- Improved logging in `message.py`.\n- Resolved various linting issues.\n\n## Changes in 0.36\n\n**Features and Improvements**:\n\n- refactored [NCLM ca handler](docs/nclm.md) using the external REST-API\n- [ca handler](docs/digicert.md) using the [DigiCert CertCentral API](https://dev.digicert.com/en/certcentral-apis.html)\n- [ca handler](docs/entrust.md) using the Entrust ECS Enterprise API\n- [EAB Profiling support](docs/eab_profiling.md) in Microsoft CA handlers\n- [#187](https://github.com/grindsa/acme2certifier/pull/187) url option for mscertsrv ca handler\n- subject profiling feature\n- [strip down python-impacket module](https://github.com/grindsa/acme2certifier/blob/master/docs/mswcce.md#local-installation) in docker images\n- [strip down impacket RPM package](https://github.com/grindsa/sbom/tree/main/rpm-repo/RPMs/rhel9)\n- YAML config file format supported in [EAB-Profiling feature](docs/eab_profiling.md)\n- Upgrade Container images to Ubuntu 24.04\n\n**Bugfixes**:\n\n- openssl-ca-handler: basicConstraints extension will not be marked as critical anymore\n- openssl-ca-handler: subjectkeyidentifier extension will not be marked as critical anymore\n- fall-back option to python-openssl for Redhat deployments\n- detect and handle django installations on Debian/Ubuntu systems\n- automated schema updates in case of RPM updates\n\n## Changes in 0.35\n\n**Features and Improvements**:\n\n- [#153](https://github.com/grindsa/acme2certifier/issues/153) Kerberos support in [mscertsrv_handler](docs/mscertsrv.md)\n- allowed_domainlist checking in [mswcce_handler](docs/mswcce.md)\n- `timeout` parameter in [ms-wcce_handler](docs/mswcce.md) to specify an enrollment timeout\n- new [tool to validate eab-files](docs/eab_profiling.md#profile-verification)\n- [#165](https://github.com/grindsa/acme2certifier/issues/165) [EAB profiling](docs/eab_profiling.md#enrollment-profiling-via-external-account-binding) for ejbca_handler\n- [#166](https://github.com/grindsa/acme2certifier/issues/166) [EAB profiling](docs/acme_ca.md#eab-profiling) for acme_ca_handler\n- documentation for active/active setup on [Alma9](docs/a2c-alma-loadbalancing.md) and [Ubuntu 22.04](docs/a2c-ubuntu-loadbalancing.md)\n- [#165](https://github.com/grindsa/acme2certifier/issues/165) documentation of [external database support](docs/external_database_support.md) via django_handler\n\n**Bugfixes**:\n\n- `acme_srv.cfg` will be preserved in case of RPM updates\n- apache2_wsgi docker image will be tagged with `latest`\n- [#166](https://github.com/grindsa/acme2certifier/issues/166) workaround for failed account lookups on smallstep-ca\n\n## Changes in 0.34\n\n**Features and Improvements**:\n\n- [Enrollment profiling via external account binding](docs/eab_profiling.md)\n- [#144](https://github.com/grindsa/acme2certifier/issues/144) configuration option to suppress product name\n- [#143](https://github.com/grindsa/acme2certifier/issues/143) template name as part of the user-agent field in wcce/wes handler\n- configuration option to limit the number of identifiers in a single order request\n- `burst` parameter in example nginx.conf to ratelimit incoming requests\n- [container images for arm64 platforms](https://hub.docker.com/layers/grindsa/acme2certifier/apache2-wsgi/images/sha256-9092e98ad23fa94dfb17534333a9306ec447b274c2e4b5bbaee0b8bc41c6becc?context=repo)\n- regression tests on arm64 platforms\n\n**Bugfixes**:\n\n- [#147](https://github.com/grindsa/acme2certifier/pull/147) correct content-type for problem+json message\n- updated [eab-example files](https://github.com/grindsa/acme2certifier/tree/master/examples/eab_handler) as hmac must be longer than 256bits\n- identifier sanitizing\n\n## Changes in 0.33.3\n\n**Features and Improvements**:\n\n- some smaller modifications run flawless on Redhat8 and derivates\n- Workflows to test rpm-deployment on RHEL8 and RHEL9\n\n## Changes in 0.33.2\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Bugfixes**:\n\n- [134](https://github.com/grindsa/acme2certifier/issues/134) - acme_srv_housekeeping\" -> value too long for \"name\" field\n- [135](https://github.com/grindsa/acme2certifier/issues/134) - acme_srv_housekeeping dbversion ist set back to 0.23.1 after container restart\n\n## Changes in 0.33.1\n\n**Bugfixes**:\n\n- [132](https://github.com/grindsa/acme2certifier/issues/132) - returning serial numbers in hex-format with leading zero\n\n## Changes in 0.33\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features and Improvements**:\n\n- Support [draft-ietf-acme-ari-02](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/02): Renewal Information (ARI) Extension\n- First version of [Insta ASA CA handler](docs/asa.md)\n- [winacme renewal-info workaround](https://github.com/grindsa/acme2certifier/issues/127)\n- better logging to ease troubleshootnig of eab\n- code refactoring to improve [f-string handling](https://pylint.pycqa.org/en/latest/user_guide/messages/convention/consider-using-f-string.html)\n\n## Changes in 0.32\n\n**Features and Improvements**:\n\n- [#114](https://github.com/grindsa/acme2certifier/issues/114) `cert_validity_adjust` parameter in openssl_ca_handler.py to limit certificate validity so that a certificate is never valid longer than any ca certificate in the certificate chain\n\n## Changes in 0.31\n\n**Features and Improvements**:\n\n- refactor `opennssl_ca_handler.py` and `xca_ca_handler.py` to replace pyopenssl\n- type hints for large parts of the project\n\n## Changes in 0.30\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n  **Features and Improvements**:\n\n  - [use http-header attributes to pass data from acme-client to ca-handler](https://github.com/grindsa/acme2certifier/blob/devel/docs/header_info.md)\n  - ProfileID support in `certifier_ca_handler.py`\n  - [Kerberos support](https://github.com/grindsa/acme2certifier/issues/119#issuecomment-1763851071) in `mswcce_ca_handler.py`\n  - [#122](https://github.com/grindsa/acme2certifier/issues/122) support of `sectigo-email-01` challenges\n\n## Changes in 0.29.2\n\n**Bugfixes**:\n\n- #119 - handling of utf-8 encoded parameters in `acme_srv.cfg`\n- adding `python3-requests-ntlm` dependency in control file for debian packages\n- multiple smaller fixes in workflow files\n\n## Changes to 0.29.1\n\n- withdrawn as released by mistake\n\n## Changes in 0.29\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features and Improvements**:\n\n- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): Certificates for IP addresses\n- Support [draft-ietf-acme-ari-01](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/01): Renewal Information (ARI) Extension\n- Interoperability testing with [Caddy](https://caddyserver.com/docs/automatic-https) as part of regular regression\n\n## Changes in 0.28\n\n**Features and Improvements**:\n\n- input validation in django deployments\n- return account status when querying the account endpoint or sending a request to `new-account` with empty payload\n- merge codescanning workflows into a single file\n\n**Bugfixes**:\n\n- [#111](https://github.com/grindsa/acme2certifier/issues/111) - Nonce handling in error responses\n- [#112](https://github.com/grindsa/acme2certifier/issues/112) - Keyrollover in Posh-ACME\n\n## Changes in 0.27\n\n**Features and Improvements**:\n\n- interoperability testing with [traefik](https://traefik.io/)\n- refactor revocation function in openxpki_ca_handler to support revocation operation in certbot\n- support pkcs7 loading in der format\n- obsolete pyopenssl in various helper functions, est_ca_handler and mscertserv_ca_handler\n\n**Bugfixes**:\n\n- sending alpn-extension in ClientHello message during tls-alpn-01 challenge validation\n- removed misleading debug messages in `openxpki_ca_handler.py`\n- support existing acme-accounts in `acme_ca_hander.py`\n- address codesmells in dockerfiles\n\n## Changes in 0.26\n\n**Features and Improvements**:\n\n- support ClientAuthentication in `openxpki_ca_handler.py` and `est_ca_handler.py` by using pkcs12 files\n- provide pkcs12 passphrases for `ejbca_ca_handler.py`, `openxpki_ca_handler.py` and `est_ca_handler.py` as environment variables\n\n**Bugfixes**:\n\n- #104 - conffile support in debian package to avoid overriding configuration files\n\n## Changes in 0.25.1\n\n**Bugfixes**:\n\n- replace obsoleted `dns.resolver.query()` with `dns.resolver.resolve()`\n\n## Changes in 0.25\n\n**Features and Improvements**:\n\n- CA handler for [EJBCA](https://www.ejbca.org/)\n- CA handler for [OpenXPKI](https://www.openxpki.org/)\n\n**Bugfixes**:\n\n- adding missing python modules to RPM spec file\n- add revocation operations to CA handler regression test suite\n\n## Changes in 0.24\n\n**Features and Improvements**:\n\n- reduce number of layers in docker images\n- Workflows are using checkout@v3 actions\n- default nginx ssl config file in rpm package corrected\n- delete seclinux configuration files after rpm installation\n- delete obsolete files from repo\n- rpm package tests during regression\n- [sbom generation](https://github.com/grindsa/sbom/tree/main/sbom/acme2certifier) as part of [docker image create worflow](.github/workflows/push_images_to_dockerhub.yml)\n- rpm and deb package generatation as part of [create release workflow](.github/workflows/create_release.yml)\n- nginx django test workflows\n\n## Changes in 0.23.2\n\n**Features and Improvements**:\n\n- [rpm](docs/install_rpm.md) and [deb](docs/install_deb.md) packages\n\n## Changes in 0.23.1\n\n**Bugfixes**:\n\n- [#99 - Authorization.value max_length too short for SAN entries](https://github.com/grindsa/acme2certifier/issues/99)\n\n## Changes in 0.23\n\n**Features and Improvements**:\n\n- Healthcheck in directory ressource [#94](https://github.com/grindsa/acme2certifier/issues/94)\n- check `acme_srv.cfg` for options starting with \"\n\n**Bugfixes**:\n\n- [#95](https://github.com/grindsa/acme2certifier/issues/95)\n- workflow django psql workflow\n- some more linting\n\n## Changes in 0.22\n\n**Features and Improvements**:\n\n- containers got migrated to Ubuntu 22.04\n- nclm handler supporting NCLM 22 and above\n\n**Bugfixes**:\n\n- [pycodestyle 2.9.1](https://pycodestyle.pycqa.org/en/2.9.1/intro.html) linting\n- time adjustment in CMPv2 workflow to avoid race condition related timeouts\n- link updates in [README.md](README.md)\n- attribute type in error responses [#92](https://github.com/grindsa/acme2certifier/issues/92)\n\n## Changes in 0.21\n\n**Features and Improvements**:\n\n- support of enrollment [hooks](docs/hooks.md)\n- `challenge_validation_timeout` parameter in [acme_srv.cfg](docs/acme_srv.md)\n- cmpv2_ca_handler using the inbuilt cmp feature from openssl 3.0\n- Github action to test certificate enrollment using CMPv2 protocol\n- Github action to test certificate enrollment from [NetGuard Certificate Lifecycle Manager](docs/nclm.md)\n\n**Bugfixes**:\n\n- RFC compliant content-type in error responses\n\n## Changes in 0.20\n\n**Features and Improvements**:\n\n- [CA handler](docs/mswcce.md) using Microsoft Windows Client Certificate Enrollment Protocol\n- asynchronous enrollment workflow using threading module\n- option to re-use certificates enrolled within a certain time window\n- workflow using [Posh-ACME](https://github.com/rmbolger/Posh-ACME)\n\n**Bugfixes**:\n\n- return challenge status when creating/polling Authorization resources\n- remove duplicated certificate extension in openssl_ca_handler.py\n- change challenge status to 'invalid' in case enrollment fails\n\n## Changes in 0.19.3\n\n**Features and Improvements**:\n\n- disable TLSv1.0 and TLSv1.1 fallback when conduction TLS-ALP=1 challenge validation\n- python3-cryptography will be installed via pip to fulfill dependencies from pyOpenssl\n- Changed encoding detection library from chardet to charset_normalizer\n- [lgtm](https://lgtm.com/projects/g/grindsa/acme2certifier/context:python) conformance\n\n## Changes in 0.19.2\n\n**Features and Improvements**:\n\n- support for django 3.x\n- workflow for application testing using win-acme\n- additional linting and pep8 conformance checks\n\n## Changes in 0.19.1\n\n**Features and Improvements**:\n\n- pep8 conformance\n- time adjustments in certmanager and django workflows\n- addressing code-scanning alerts from bandit and CodeQL\n\n## Changes in 0.19\n\n**Bugfixes**:\n\n- [Authorization polling does not trigger challenge validation anymore](https://github.com/grindsa/acme2certifier/issues/76)\n- Overcome database locking situations in django environments using sqlite3 backends\n\n**Features and Improvements**:\n\n- [RFC compliant Wildcard handling](https://github.com/grindsa/acme2certifier/issues/76)\n\n## Changes in 0.18.2\n\n**Bugfixes**:\n\n- [Fix the disabling of SSL validation in http-01 challenge](https://github.com/grindsa/acme2certifier/pull/75)\n\n## Changes in 0.18.1\n\n**Features and Improvements**:\n\n- absolute path support for CA- and EABhandler\n\n**Bugfixes**:\n\n- fixed race condition in push_to_docker workflow\n\n## Changes in 0.18\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features and Improvements**:\n\n- [proxy support](docs/proxy_support.md) for http and tls-alpn challenge validation and in several ca-handlers\n- [acme_ca_handler](docs/acme_ca.md)\n  - support for account registration and http_challenge validation\n- [openssl_ca_handler](docs/openssl.md):\n  - `cn_enforce` parameter to enforce setting a common name in certificate\n  - `whitelist` parameter got renamed to `allowed_domainlist`\n  - `blocklist` parameter got renamed to `blocked_domainlist`\n- [xca_ca_handler](docs/xca.md):\n  - `cn_enforce` parameter to enforce setting a common name in certificate\n\n## Changes in 0.17.1\n\n**Bugfixes**:\n\n- python request module - version pinning to 2.25.1\n\n## Changes in 0.17\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django_handler\n\n**Features**:\n\n- [Generic ACME protocol handler](docs/acme_ca.md)\n- CA handler for [acme2dfn](https://github.com/pfisterer/acme2dfn)\n- wsgi_db_handler: allow DB file path configuration\n- allow setting config file location via environment variable\n\n**Improvements**:\n\n- `acme` module has been renamed to `acme_srv` to avoid naming clashes with [acme-python](https://acme-python.readthedocs.io/en/stable/)\n- allow GET method for newnonce\n- don't verify SSL certificate during http-01 challenge validation\n\n## Changes in 0.16\n\n**Features**:\n\n- CA-Handler configuration via environment variables:\n  - cmp_ca_handler: ref-num and passphrase\n  - certifier_ca_handler: api_user, api_password\n  - est_ca_handler: est_host, est_user, est_password\n  - mscertsrv_ca_handler: host, user, password\n  - nclm_ca_handler: api_user, api_password\n  - openssl_ca_handler: passphrase\n  - xca_ca_handler: passphrase\n\n**Bugfixes**:\n\n- don't overwrite group ownership for volume folder\n- don't copy ca_handler file if a valid ca_handler was defined under `CAhandler` section in acme_srv.cfg\n- django migrations files will get stored on volume\n- avoidance of KU/EKU duplicates when using templates in xca_ca_handler\n- alpn challenge handling in django deployments\n- fix for handling of empty challenges\n- more robust DNS challenge validation\n\n**Other improvements**:\n\n- [CodeCoverage measurement](https://app.codecov.io/gh/grindsa/acme2certifier/) via codecov.io\n- Switch to [acme.sh:latest](https://hub.docker.com/r/neilpang/acme.sh) in CI pipeline\n- Regression test-cases for django deployments using either mariadb or postgres backends\n- experimental CLI framework (not yet useable)\n\n## Changes in 0.15.3\n\n**Upgrade notes**:\n\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django.handler\n\n**Bugfixes**:\n\n- fix for `type` field length in `Challenge` table\n\n## Changes in 0.15.2\n\n**Bugfixes**:\n\n- additional fixes for dns-01 challenge validation (handling for \\*.foo.bar and foo.bar in the same csr)\n\n## Changes in 0.15.1\n\n**Bugfixes**:\n\n- fixes for dns-01 challenge validation\n- default ku settings when using xca templates\n\n## Changes in 0.15\n\n**Upgrade notes**:\n\n- You need to run the upgrade-script after updating the package\n\n**Features**:\n\n- support for [tls-alpn-01](https://tools.ietf.org/html/rfc8737) challenges\n- eab kid logging and reporting\n\n**Bugfixes**:\n\n- database scheme versioning\n\n## Changes in 0.14\n\n**Upgrade notes**:\n\n- You need to run the upgrade-script after updating the package\n\n**Features**:\n\n- support for [External Account Binding](docs/eab.md)\n\n**Bugfixes**:\n\n- `acme2certifier_wsgi.py`- newaccount() - initialize `Account()` class as context handler\n\n## Changes in 0.13.1\n\n**Upgrade notes**:\n\n- You need to run the upgrade-script after updating the package\n\n**Bugfixes**:\n\n- `helper.py`- fqdn_resolve() - resolve AAAA records\n- `helper.py`- url_gete() - ipv4 fallback during http challenge validation\n\n## Changes in 0.13\n\n**Features**:\n\n- template support in `xca_handler.py` and `nclm_ca_handler.py`\n- docker images at [ghcr.io](https://github.com/grindsa?tab=packages)\n\n**Bugfixes/Improvements**:\n\n- refactor `nclm_ca_handler.py`\n- refactor `certifier_ca_handler.py`\n- workflows for\n  - code-scanning (CodeQL and Bandit)\n  - ca_handler tests\n  - phonito security scans\n\n## Changes in 0.12.1\n\n**Upgrade notes**:\n\n- You need to run the upgrade-script after updating the package\n\n**Bugfixes**:\n\n- `helper.py`- fqdn_resolve() - resolve AAAA records\n\n## Changes in 0.12\n\n**Upgrade notes**:\n\n- its enough to run the upgrade script. Depending on your configuration you need to either run\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django.handler\n\n**Features**:\n\n- docker images containing nginx\n- readymade images at [dockerhub](https://hub.docker.com/r/grindsa/acme2certifier)\n\n**Bugfixes/Improvements**:\n\n- several fixes in unit-tests\n- unit-tests are split into separate files\n- unittests for `certifier_ca_handler.py`\n- documentation updates\n- Github actions to test\n  - certificate enrollment for all four containerized deployment options\n  - tnauth functionality\n  - image creation and dockerhub upload\n\n## Changes in 0.11.1\n\n**Bugfixes**:\n\n- `cmp_ca_handler.py`- avoid crash if tmp_dir has not been specified in config-files\n- `order.py` - expiry date will be added during authz creation\n- `authorization.py` - corner cases handling in case authz expiry is set to 0\n- `wiki-update.yml` - checkout from `grindsa/github-wiki-publish-action@customize_wiki_title`\n- `*.md` - meta tag \"wiki-name\" added\n\n## Changes in 0.11\n\n**Upgrade notes**:\n\n- take a backup of your `acme_srv.db` before doing the upgrade\n- update your `db_handler.py` with the latest version from the `examples/db_handler` directory\n- database scheme gets updated. Please run either\n  - `tools/db_update.py` when using the wsgi_handler or\n  - `tools/django_update.py` in case you are using the django.handler\n- orders and authorization expire based on (pre)configured timers\n- default expiration timer is 86400 seconds and can be adjusted in `acme_srv.cfg`.\n- auto expiration can be disabled in `acme_srv.cfg`. Check [docs/acme_srv.md](docs/acme_srv.md) for further information.\n- the expiration checks and order/authorization invalidation will be triggered in case a client accesses an `order` or `authorization` resource.  It is recommended to run the script `tools/invalidator.py` after the upgrade to manually check and invalidate expired authorizations and orders and update issuing- and expiration date in the certificate table.\n\n**Features**:\n\n- ca_handler kann be specified in `acme_srv.cfg`\n- certifier_ca_handler.py - handling of der encoded certificates in trigger() method\n- issuing date and expiration date will be stored in the `certificate` table\n- `xca_ca_handler`: new variable `issuing_ca_key`\n- basic [reporting and housekeeping](docs/housekeeping.md)\n- order and authorization expiration\n- method to remove expired certificates from database. Check the `certificate_cleanup` method [docs/housekeeping.md](docs/housekeeping.md) for further information\n- database versioning and error logging in case of version mismatch\n\n**Bugfixes**\\*:\n\n- Base64 encoding `certifier_trigger.sh` (removed blanks by using `-w 0`)\n- improved exception handling in case of database-errors\n\n## Changes in 0.10\n\n**Upgrade notes**:\n\n- database scheme gets updated. Depending on the db_handler you need to:\n  - run `py manage.py makemigrations && py manage.py migrate` in case you use the django_handler.\n  - execute the `tools/db_upgrade.py` script when using the wsgi_handler\n\n**Features**:\n\n- http_x_forward header support\n- configurable tos\n- option to disable contact check\n- option to disable tos check\n\n**Bugfixes**:\n\n- mscertsrv_ca_handler: [#37 - pkcs#7 to pem conversion](https://github.com/grindsa/acme2certifier/issues/37)\n- mscertsrv_ca_handler: CRLF to LF conversion\n- [#35 rfc608  compliant contact checking](https://github.com/grindsa/acme2certifier/issues/35)\n- xca_handler: [#38 - prevent error message leakage to client](https://github.com/grindsa/acme2certifier/issues/38)\n\n## Changes in 0.9\n\n**Features**:\n\n- option to mandate the usage of ecc keys\n- openssl_handler: \"save_as_hex\" option\n- openssl_handler: black/whitlist support\n- openssl_hanlder: option to configure customized cert extensions\n- option to configure custom dns resolvers\n- xca_handler\n- Additional client support (lego and win-acme)\n\n**Bugfixes**:\n\n- updated license\n- empty CRL handling\n- string parsing in `b64_url_encode()`\n- py3 support for est_handler\n- [#9 - base64-parsing of dns challenge](https://github.com/grindsa/acme2certifier/issues/9)\n- openssl_handler: set correct x509 version\n- openssl_handler: mandatory cert-extensions\n- harmonization of apache config files\n- migration support for docker_django deployment\n\n## Changes in 0.8\n\n**Features**:\n\n- Challenge polling\n- Support for CA polling and call-backs\n- Certificate profiling in openssl handler\n- Ssl support\n- Container deployments\n- Django project with mysql as backend database\n\n## Changes in 0.7\n\n**Features**:\n\n- support ECC keys\n- key update and key roll-over support\n- generic CMPv2 handler\n\n## Changes in 0.6\n\n**Features**:\n\n- EST and certsrv support\n\n## Changes in 0.5\n\n**Features**:\n\n- CSR validation against order identifiers\n\n## Changes in 0.4\n\n**Features**:\n\n- experimental TNAuthList identifier and tkauth-01 challenge support\n- compatibility with Python3\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) 2018  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    dkb-robo  Copyright (C) 2018  grindsa\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n# acme2certifier\n\n![GitHub release](https://img.shields.io/github/release/grindsa/acme2certifier.svg)\n![GitHub last commit (branch)](https://img.shields.io/github/last-commit/grindsa/acme2certifier/master.svg?label=last%20commit%20into%20master)\n![GitHub last commit (branch)](https://img.shields.io/github/last-commit/grindsa/acme2certifier/devel.svg?label=last%20commit%20into%20devel)\n[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2581/badge)](https://bestpractices.coreinfrastructure.org/projects/2581)\n\n[![Codecov main](https://img.shields.io/codecov/c/github/grindsa/acme2certifier/master?label=test%20coverage%20master)](https://app.codecov.io/gh/grindsa/acme2certifier/tree/master)\n[![Codecov devel](https://img.shields.io/codecov/c/github/grindsa/acme2certifier/devel?label=test%20coverage%20devel)](https://app.codecov.io/gh/grindsa/acme2certifier/tree/devel)\n\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=grindsa_acme2certifier&metric=security_rating)](https://sonarcloud.io/summary/overall?id=grindsa_acme2certifier)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=grindsa_acme2certifier&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=grindsa_acme2certifier)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=grindsa_acme2certifier&metric=reliability_rating)](https://sonarcloud.io/summary/overall?id=grindsa_acme2certifier)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=grindsa_acme2certifier&metric=alert_status)](https://sonarcloud.io/summary/overall?id=grindsa_acme2certifier)\n\n**acme2certifier** is a development project aimed at creating an **ACME protocol proxy**. Its primary goal is to enable **ACME services** for **CA servers** that do not natively support this protocol.\n\nThe project consists of two main libraries:\n\n- **`acme_srv/*.py`** – Implements ACME server functionality based on [RFC 8555](https://tools.ietf.org/html/rfc8555).\n- **`ca_handler.py`** – Provides an **interface to CA servers**, designed to be modular for easy adaptation to various CA systems.\n  The currently available handlers are listed below:\n\n## Supported CA Handlers\n\n| Feature Support                                                                                                                                | Enrollment (E) | Revocation (R) | [EAB Profiling (P)](docs/eab_profiling.md) |\n| ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -------------- | ------------------------------------------ |\n| [DigiCert® CertCentral](docs/digicert.md)                                                                                                      | ✅             | ✅             | ✅                                         |\n| [Entrust ECS Enterprise](docs/entrust.md)                                                                                                      | ✅             | ✅             | ✅                                         |\n| [EJBCA](docs/ejbca.md)                                                                                                                         | ✅             | ✅             | ✅                                         |\n| [Generic ACME Handler](docs/acme_ca.md) (LetsEncrypt, BuyPass.com, ZeroSSL)                                                                    | ❌             | ❌             | ✅                                         |\n| [Generic CMPv2 Handler](docs/cmp.md)                                                                                                           | ✅             | ❌             | ❌                                         |\n| [Generic EST Handler](docs/est.md)                                                                                                             | ✅             | ❌             | ❌                                         |\n| [Hashicorp Vault](docs/vault.md)                                                                                                               | ✅             | ✅             | ✅                                         |\n| [Insta ActiveCMS](docs/asa.md)                                                                                                                 | ✅             | ✅             | ✅                                         |\n| [Microsoft Certificate Enrollment Web Services](docs/mscertsrv.md)                                                                             | ✅             | ❌             | ✅                                         |\n| [Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE)](docs/mswcce.md)                                                           | ✅             | ❌             | ✅                                         |\n| [NetGuard Certificate Lifecycle Manager](docs/nclm.md)                                                                                         | ✅             | ✅             | ✅                                         |\n| [NetGuard Certificate Manager/Insta Certifier](docs/certifier.md)                                                                              | ✅             | ✅             | ✅                                         |\n| [OpenSSL](docs/openssl.md)                                                                                                                     | ✅             | ✅             | ❌                                         |\n| [OpenXPKI](docs/openxpki.md)                                                                                                                   | ✅             | ✅             | ✅                                         |\n| [XCA](docs/xca.md)                                                                                                                             | ✅             | ✅             | ✅                                         |\n\nFor the latest updates and additional documentation, visit the project's homepage:\n[**acme2certifier on GitHub**](https://github.com/grindsa/acme2certifier)\n\n______________________________________________________________________\n\n## 📌 ChangeLog\n\nRelease notes and changelogs are available at:\n[**GitHub Releases**](https://github.com/grindsa/acme2certifier/releases)\n\n______________________________________________________________________\n\n## 🛠 ACME Client Compatibility\n\nThe following ACME clients are **regularly tested** for compatibility:\n\n- [acme.sh](https://github.com/Neilpang/acme.sh)\n- [acmeshell](https://github.com/cpu/acmeshell/)\n- [Caddy](https://caddyserver.com/docs/automatic-https)\n- [Certbot](https://certbot.eff.org/)\n- [cert-manager](docs/cert-mgr.md)\n- [dehydrated](https://www.rfc-editor.org/rfc/rfc8823.html#name-use-of-acme-for-issuing-end)\n- [lego](https://github.com/go-acme/lego)\n- [traefik](https://traefik.io/)\n- [Posh-ACME](https://github.com/rmbolger/Posh-ACME)\n- [win-acme](https://www.win-acme.com/)\n\nOther clients are **on the list for future testing**.\nIf you test additional ACME clients, feel free to raise an [issue](https://github.com/grindsa/acme2certifier/issues/new) if something does not work as expected.\n\n[List of command-line parameters used for testing](docs/rfc8823_email_identifier.md)\n\n______________________________________________________________________\n\n## 🚀 Features\n\n- **ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) compliant** server implementation, including:\n  - [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html) – **TLS ALPN-01 Challenge**\n  - [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html) – **IP Address Certificates**\n  - [RFC 8823](https://www.rfc-editor.org/rfc/rfc8823.html) - **Automatic Certificate Management Environment for End-User S/MIME Certificates**\n  - [RFC 9773](https://datatracker.ietf.org/doc/rfc9773/) - **ACME Renewal Information (ARI) Extension**\n  - [ACME Profiles Extension](docs/acme_profiling.md)\n  - **TNAuthList identifiers** ([TNAuthList Profile](docs/tnauthlist.md))\n  - [RFC 9447 - Automated Certificate Management Environment (ACME) Challenges Using an Authority Token](https://www.rfc-editor.org/rfc/rfc9447)\n  - [Certificate Polling](docs/poll.md) and [Callbacks](docs/trigger.md) for CA servers.\n\nSupported challenge types:\n\n- [http-01](https://tools.ietf.org/html/rfc8555#section-8.3)\n- [dns-01](https://tools.ietf.org/html/rfc8555#section-8.4)\n- [email-reply-00](https://www.rfc-editor.org/rfc/rfc8823.html#name-use-of-acme-for-issuing-end)\n- [tls-alpn-01](https://tools.ietf.org/html/rfc8737)\n- [tkauth-01](https://www.rfc-editor.org/rfc/rfc9447)\n\n______________________________________________________________________\n\n## 📦 Installation\n\n**acme2certifier** can be installed as:\n\n- **WSGI application** (Apache2/Nginx)\n- **Django project** (allows using alternative databases)\n\nThe fastest and most convenient way to install acme2certifier is to use docker containers. There are ready made images available at [dockerhub](https://hub.docker.com/r/grindsa/acme2certifier) and [ghcr.io](https://github.com/grindsa?tab=packages&ecosystem=container) as well as [instructions to build your own container](examples/Docker/).\nIn addition rpm packages for AlmaLinux/CentOS Stream/Redhat EL 9 and deb packages for Ubuntu 22.04 will be provided with every release.\n\nInstallation guides:\n\n- [RPM Installation (AlmaLinux 9)](docs/install_rpm.md)\n- [DEB Installation (Ubuntu 22.04)](docs/install_deb.md)\n- [Docker Build Instructions](examples/Docker/)\n- [Apache2 WSGI Setup (Ubuntu 22.04)](docs/install_apache2_wsgi.md)\n- [Nginx WSGI Setup (Ubuntu 22.04)](docs/install_nginx_wsgi_ub22.md)\n\n## Software Bill Of Material\n\n[SBOMs](https://www.linuxfoundation.org/blog/blog/what-is-an-sbom) for all containers will be automatically created during build process and stored in [my SBOM repository](https://github.com/grindsa/sbom/tree/main/sbom/acme2certifier)\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](docs/CONTRIBUTING.md) for details on my code of conduct, and the process for submitting pull requests. Please note that I have a life besides programming. Thus, expect a delay in answering.\n\n## Versioning\n\nI use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/grindsa/dkb-robo/tags).\n\n## License\n\nThis project is licensed under the GPLv3 - see the [LICENSE](LICENSE) file for details\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Security Policy -->\n\n# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 0.42.x  | :white_check_mark: |\n| 0.41.x  | :white_check_mark: |\n| \\< 0.41 | :x:  |\n\n## Reporting a Vulnerability\n\nPlease report security vulnerabilities directly to grindelsack@gmail.com and provide the following information:\n\n- A summary of the problem\n- Used software version and deployment mode\n- The actual behaviour\n- The expected behaviour\n- Steps to replicate the problem (if they there are any)\n- Debug logs from acme2certifier\n\nPreferred-Languages: en, de.\n"
  },
  {
    "path": "acme_srv/__init__.py",
    "content": "\"\"\"init.py\"\"\"\n\nfrom .version import __version__\n"
  },
  {
    "path": "acme_srv/account.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Refactored Account class with improved design and maintainability\"\"\"\n\nfrom __future__ import print_function\nimport json\nfrom typing import List, Tuple, Dict, Optional\nfrom dataclasses import dataclass, field\nfrom acme_srv.helper import (\n    generate_random_string,\n    validate_email,\n    date_to_datestr,\n    load_config,\n    eab_handler_load,\n    b64decode_pad,\n    error_dic_get,\n    uts_to_date_utc,\n    uts_now,\n)\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.message import Message\n\nfrom acme_srv.signature import Signature\n\n# --- ExternalAccountBinding class integrated here ---\nimport json\nfrom acme_srv.helper import b64decode_pad\n\n\nDB_ERROR_MSG = \"Database error\"\n\n\nclass ExternalAccountBinding:\n    \"\"\"Encapsulates EAB validation and signature verification logic.\"\"\"\n\n    def __init__(self, logger, eab_handler, server_name=None):\n        self.logger = logger\n        self.eab_handler = eab_handler\n        self.server_name = server_name\n\n    def get_kid(self, protected: str) -> str:\n        \"\"\"Extract key identifier (kid) from protected header.\"\"\"\n        self.logger.debug(\"ExternalAccountBinding.get_kid()\")\n        try:\n            protected_dic = json.loads(b64decode_pad(self.logger, protected))\n        except Exception as err:\n            self.logger.error(\"Failed to decode protected header: %s\", err)\n            protected_dic = None\n\n        if isinstance(protected_dic, dict):\n            eab_key_id = protected_dic.get(\"kid\", None)\n        else:\n            eab_key_id = None\n        self.logger.debug(\"ExternalAccountBinding.get_kid() ended with: %s\", eab_key_id)\n        return eab_key_id\n\n    def compare_jwk(self, protected: dict, payload: str) -> bool:\n        \"\"\"Compare JWK from outer header with JWK in EAB payload.\"\"\"\n        self.logger.debug(\"ExternalAccountBinding.compare_jwk()\")\n        result = False\n        if \"jwk\" in protected:\n            jwk_outer = protected[\"jwk\"]\n            jwk_inner = b64decode_pad(self.logger, payload)\n            jwk_inner = json.loads(jwk_inner)\n            if json.dumps(jwk_outer, sort_keys=True) == json.dumps(\n                jwk_inner, sort_keys=True\n            ):\n                result = True\n            else:\n                self.logger.error(\"JWK from outer and inner JWS do not match\")\n                self.logger.debug(\"outer: %s\", jwk_outer)\n                self.logger.debug(\"inner: %s\", jwk_inner)\n        else:\n            self.logger.error(\"No JWK in protected header\")\n        self.logger.debug(\"ExternalAccountBinding.compare_jwk() ended with: %s\", result)\n        return result\n\n    def verify_signature(self, content: dict, mac_key: str) -> tuple:\n        \"\"\"Verify EAB signature.\"\"\"\n        self.logger.debug(\"ExternalAccountBinding.verify_signature()\")\n        if content and mac_key:\n            signature = Signature(None, self.server_name, self.logger)\n            jwk_ = json.dumps({\"k\": mac_key, \"kty\": \"oct\"})\n            (sig_check, error) = signature.eab_check(json.dumps(content), jwk_)\n        else:\n            sig_check = False\n            error = None\n        self.logger.debug(\n            \"ExternalAccountBinding.verify_signature() ended with: %s: %s\",\n            sig_check,\n            error,\n        )\n        return (sig_check, error)\n\n    def verify(self, payload: dict, err_msg_dic: dict) -> tuple:\n        \"\"\"Check for external account binding and verify signature.\"\"\"\n        self.logger.debug(\"ExternalAccountBinding.verify()\")\n        eab_kid = self.get_kid(payload[\"externalaccountbinding\"][\"protected\"])\n        if eab_kid:\n            with self.eab_handler(self.logger) as eab_handler:\n                eab_mac_key = eab_handler.mac_key_get(eab_kid)\n        else:\n            eab_mac_key = None\n        if eab_mac_key:\n            (result, error) = self.verify_signature(\n                payload[\"externalaccountbinding\"], eab_mac_key\n            )\n            if result:\n                code = 200\n                message = None\n                detail = None\n            else:\n                code = 403\n                message = err_msg_dic[\"unauthorized\"]\n                detail = \"EAB signature verification failed\"\n                self.logger.error(\"EAB verification returned an error: %s\", error)\n        else:\n            code = 403\n            message = err_msg_dic[\"unauthorized\"]\n            detail = \"EAB kid lookup failed\"\n        self.logger.debug(\"ExternalAccountBinding.verify() ended with: %s\", code)\n        return (code, message, detail)\n\n    def check(self, protected: dict, payload: dict, err_msg_dic: dict) -> tuple:\n        \"\"\"Check for external account binding, compare JWK, and verify signature.\"\"\"\n        self.logger.debug(\"ExternalAccountBinding.check()\")\n\n        if (\n            self.eab_handler\n            and protected\n            and payload\n            and \"externalaccountbinding\" in payload\n            and payload[\"externalaccountbinding\"]\n        ):\n            jwk_compare = self.compare_jwk(\n                protected, payload[\"externalaccountbinding\"][\"payload\"]\n            )\n            if jwk_compare and \"protected\" in payload[\"externalaccountbinding\"]:\n                return self.verify(payload, err_msg_dic)\n            else:\n                code = 403\n                message = err_msg_dic[\"malformed\"]\n                detail = \"Malformed request\"\n        else:\n            code = 403\n            message = err_msg_dic[\"externalaccountrequired\"]\n            detail = \"External account binding required\"\n        self.logger.debug(\"ExternalAccountBinding.check() ended with: %s\", code)\n        return (code, message, detail)\n\n\nclass AccountDatabaseError(Exception):\n    \"\"\"Exception raised for database-related errors in Account operations.\"\"\"\n\n    pass\n\n\nclass AccountRepository:\n    \"\"\"Repository for all Account-related database operations.\"\"\"\n\n    def __init__(self, dbstore, logger=None):\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def lookup_account(self, field: str, value: str) -> Optional[Dict[str, str]]:\n        \"\"\"Look up an account in the database.\"\"\"\n        try:\n            return self.dbstore.account_lookup(field, value)\n        except Exception as err:\n            self.logger.critical(\"Database error during account lookup: %s\", err)\n            raise AccountDatabaseError(f\"Failed to look up account: {err}\") from err\n\n    def add_account(self, data_dic: Dict[str, str]) -> Tuple[Optional[str], bool]:\n        \"\"\"Add a new account to the database.\"\"\"\n        try:\n            return self.dbstore.account_add(data_dic)\n        except Exception as err:\n            self.logger.critical(\"Database error while adding account: %s\", err)\n            raise AccountDatabaseError(f\"Failed to add account: {err}\") from err\n\n    def update_account(self, data_dic: Dict[str, str], active: bool = True) -> bool:\n        \"\"\"Update an account in the database.\"\"\"\n        try:\n            return self.dbstore.account_update(data_dic, active)\n        except Exception as err:\n            self.logger.critical(\"Database error while updating account: %s\", err)\n            raise AccountDatabaseError(f\"Failed to update account: {err}\") from err\n\n    def delete_account(self, account_name: str) -> bool:\n        \"\"\"Delete an account from the database.\"\"\"\n        try:\n            return self.dbstore.account_delete(account_name)\n        except Exception as err:\n            self.logger.critical(\"Database error while deleting account: %s\", err)\n            raise AccountDatabaseError(f\"Failed to delete account: {err}\") from err\n\n    def load_jwk(self, account_name: str) -> Optional[Dict[str, str]]:\n        \"\"\"Load the JWK for a given account.\"\"\"\n        try:\n            return self.dbstore.jwk_load(account_name)\n        except Exception as err:\n            self.logger.critical(\"Database error while loading JWK: %s\", err)\n            raise AccountDatabaseError(f\"Failed to load JWK: {err}\") from err\n\n\n@dataclass\nclass AccountConfiguration:\n    \"\"\"Configuration for the Account class.\"\"\"\n\n    ecc_only: bool = False\n    contact_check_disable: bool = False\n    tos_check_disable: bool = False\n    inner_header_nonce_allow: bool = False\n    tos_url: Optional[str] = None\n    eab_check: bool = False\n    eab_handler: Optional[object] = None\n    path_dic: Dict[str, str] = field(\n        default_factory=lambda: {\"acct_path\": \"/acme/acct/\"}\n    )\n\n\n@dataclass\nclass AccountData:\n    \"\"\"Data structure for account information.\"\"\"\n\n    name: str\n    alg: str\n    jwk: Dict[str, str]\n    contact: List[str]\n    eab_kid: Optional[str] = None\n    status: str = \"valid\"\n    created_at: Optional[str] = None\n\n\nclass Account:\n    \"\"\"Refactored ACME server class.\"\"\"\n\n    def __init__(self, debug: bool = False, srv_name: str = None, logger=None):\n        self.server_name = srv_name\n        self.logger = logger\n        self.dbstore = DBstore(debug, self.logger)\n        self.repository = AccountRepository(self.dbstore, self.logger)\n        self.message = Message(debug, self.server_name, self.logger)\n        self.config = AccountConfiguration()\n        self.err_msg_dic = error_dic_get(self.logger)\n\n    def __enter__(self) -> \"Order\":\n        \"\"\"Enter the context manager, loading configuration.\"\"\"\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args) -> None:\n        \"\"\"\n        Exit the context manager. (No-op, placeholder for cleanup.)\n        \"\"\"\n\n    def _load_configuration(self):\n        \"\"\"Load configuration into the AccountConfiguration dataclass.\"\"\"\n        self.logger.debug(\"Account._load_configuration()\")\n        config_dic = load_config()\n\n        self.config.inner_header_nonce_allow = config_dic.getboolean(\n            \"Account\", \"inner_header_nonce_allow\", fallback=False\n        )\n        self.config.ecc_only = config_dic.getboolean(\n            \"Account\", \"ecc_only\", fallback=False\n        )\n        self.config.tos_check_disable = config_dic.getboolean(\n            \"Account\", \"tos_check_disable\", fallback=False\n        )\n        self.config.contact_check_disable = config_dic.getboolean(\n            \"Account\", \"contact_check_disable\", fallback=False\n        )\n\n        if \"EABhandler\" in config_dic:\n            self.logger.debug(\"Account._load_configuration(): loading eab_handler\")\n            self.config.eab_check = True\n            if \"eab_handler_file\" in config_dic[\"EABhandler\"]:\n                eab_handler_module = eab_handler_load(self.logger, config_dic)\n                if eab_handler_module:\n                    self.config.eab_handler = eab_handler_module.EABhandler\n                else:\n                    self.logger.critical(\"EABHandler could not get loaded\")\n            else:\n                self.logger.critical(\"EABHandler configuration incomplete\")\n\n        self.config.tos_url = config_dic.get(\"Directory\", \"tos_url\", fallback=None)\n        if config_dic.get(\"Directory\", \"url_prefix\", fallback=None):\n            self.config.path_dic = {\n                k: config_dic.get(\"Directory\", \"url_prefix\") + v\n                for k, v in self.config.path_dic.items()\n            }\n        self.logger.debug(\"Account._load_configuration() ended\")\n\n    def _add_account_to_db(\n        self, account_data: AccountData\n    ) -> Tuple[int, str, Optional[Dict[str, str]]]:\n        \"\"\"Add a new account to the database.\"\"\"\n        self.logger.debug(\"Account._add_account_to_db(%s)\", account_data.name)\n        try:\n            # convert dict and list to string\n            account_data.jwk = json.dumps(account_data.jwk)\n            account_data.contact = json.dumps(account_data.contact)\n            db_name, is_new = self.repository.add_account(account_data.__dict__)\n            if is_new:\n                self.logger.debug(\n                    \"Account._add_account_to_db() ended with: 201, %s\", db_name\n                )\n                return 201, db_name, None\n            self.logger.debug(\n                \"Account._add_account_to_db() ended with: 200, %s\", db_name\n            )\n            return 200, db_name, None\n\n        except Exception as err:\n            self.logger.critical(\"Database error while adding account: %s\", err)\n            return 500, self.err_msg_dic[\"serverinternal\"], DB_ERROR_MSG\n\n    def _validate_contact(self, contact: List[str]) -> Tuple[int, str, str]:\n        \"\"\"Validate contact information.\"\"\"\n        self.logger.debug(\"Account._validate_contact()\")\n        if not contact:\n            return 400, self.err_msg_dic[\"malformed\"], \"Contact information is missing\"\n        if not validate_email(self.logger, contact):\n            return (\n                400,\n                self.err_msg_dic[\"invalidcontact\"],\n                \"Invalid contact information\",\n            )\n        return 200, None, None\n\n    def _check_tos(self, content: Dict[str, str]) -> Tuple[int, str, str]:\n        \"\"\"check terms of service\"\"\"\n        self.logger.debug(\"Account._check_tos()\")\n        if \"termsofserviceagreed\" in content:\n            self.logger.debug(\"tos:%s\", content[\"termsofserviceagreed\"])\n            if content[\"termsofserviceagreed\"]:\n                code = 200\n                message = None\n                detail = None\n            else:\n                code = 403\n                message = self.err_msg_dic[\"useractionrequired\"]\n                detail = \"Terms of service must be agreed\"\n        else:\n            self.logger.debug(\"no tos statement found.\")\n            code = 403\n            message = self.err_msg_dic[\"useractionrequired\"]\n            detail = \"termsofserviceagreed flag missing\"\n\n        self.logger.debug(\"Account._check_tos() ended with:%s\", code)\n        return (code, message, detail)\n\n    def _create_account(\n        self, payload: Dict[str, str], protected: Dict[str, str]\n    ) -> Tuple[int, str, str]:\n        \"\"\"Create a new account.\"\"\"\n        self.logger.debug(\"Account._create_account()\")\n        account_name = generate_random_string(self.logger, 12)\n        contact_list = payload.get(\"contact\", [])\n\n        # tos check\n        if self.config.tos_url and not self.config.tos_check_disable:\n            (code, message, detail) = self._check_tos(payload)\n            if code != 200:\n                return code, message, detail\n\n        # EAB check\n        if self.config.eab_check:\n            eab_handler = ExternalAccountBinding(\n                self.logger, self.config.eab_handler, self.server_name\n            )\n            code, message, detail = eab_handler.check(\n                protected, payload, self.err_msg_dic\n            )\n            if code != 200:\n                return code, message, detail\n\n        # Validate contact information\n        if not self.config.contact_check_disable:\n            code, message, detail = self._validate_contact(contact_list)\n            if code != 200:\n                return code, message, detail\n\n        # Prepare account data\n        account_data = AccountData(\n            name=account_name,\n            alg=protected[\"alg\"],\n            jwk=protected[\"jwk\"],\n            contact=contact_list,\n            created_at=date_to_datestr(uts_now()),\n        )\n        if self.config.eab_check:\n            eab_handler = ExternalAccountBinding(\n                self.logger, self.config.eab_handler, self.server_name\n            )\n            eab_kid = eab_handler.get_kid(\n                payload[\"externalaccountbinding\"][\"protected\"]\n            )\n            if eab_kid:\n                account_data.eab_kid = eab_kid\n\n        # Add account to database\n        return self._add_account_to_db(account_data)\n\n    def _parse_query(self, account_name: str) -> Dict[str, str]:\n        \"\"\"update contacts\"\"\"\n        self.logger.debug(\"Account._parse_query(%s)\", account_name)\n\n        # this is a query for account information\n        account_obj = self._lookup_account_by_name(account_name)\n        if account_obj:\n            data = self._build_account_info(account_obj)\n            data[\"status\"] = \"valid\"\n        else:\n            data = {\"status\": \"invalid\"}\n\n        self.logger.debug(\"Account._parse_query() ended\")\n        return data\n\n    def _onlyreturnexisting(\n        self, protected: Dict[str, str], payload: Dict[str, str]\n    ) -> Tuple[int, str, str]:\n        \"\"\"check onlyreturnexisting\"\"\"\n        self.logger.debug(\"Account._onlyreturnexisting(}\")\n\n        if \"onlyreturnexisting\" in payload:\n            if payload[\"onlyreturnexisting\"]:\n\n                if \"jwk\" in protected:\n                    result = self._lookup_account_by_field(\n                        json.dumps(protected[\"jwk\"]), \"jwk\"\n                    )\n                    if result:\n                        code = 200\n                        message = result[\"name\"]\n                        detail = self._parse_query(message)\n                    else:\n                        code = 400\n                        message = self.err_msg_dic[\"accountdoesnotexist\"]\n                        detail = None\n                else:\n                    code = 400\n                    message = self.err_msg_dic[\"malformed\"]\n                    detail = \"jwk structure missing\"\n\n            else:\n                code = 400\n                message = self.err_msg_dic[\"useractionrequired\"]\n                detail = \"onlyReturnExisting must be true\"\n        else:\n            code = 500\n            message = self.err_msg_dic[\"serverinternal\"]\n            detail = \"onlyReturnExisting without payload\"\n\n        self.logger.debug(\"Account.onlyreturnexisting() ended with: %s\", code)\n        return (code, message, detail)\n\n    def _handle_deactivation(\n        self, account_name: str, payload: Dict[str, str]\n    ) -> Dict[str, str]:\n        \"\"\"Handle account deactivation.\"\"\"\n        self.logger.debug(\"Account._handle_deactivation(%s)\", account_name)\n        if payload.get(\"status\", \"\").lower() == \"deactivated\":\n            code, message, detail = self._deactivate_account(account_name)\n            if code == 200:\n                return self._build_response(code, message, payload)\n            else:\n                return self._build_response(code, message, detail)\n        else:\n            return self._build_response(\n                400, self.err_msg_dic[\"malformed\"], \"Invalid status for deactivation\"\n            )\n\n    def _deactivate_account(self, account_name: str) -> Tuple[int, str, str]:\n        \"\"\"Deactivate an account.\"\"\"\n        self.logger.debug(\"Account._deactivate_account(%s)\", account_name)\n        try:\n            data_dic = {\n                \"name\": account_name,\n                \"status_id\": 7,\n                \"jwk\": f\"DEACTIVATED {uts_to_date_utc(uts_now())}\",\n            }\n            result = self.repository.update_account(data_dic, active=False)\n            if result:\n                return 200, None, None\n            else:\n                return (\n                    400,\n                    self.err_msg_dic[\"accountdoesnotexist\"],\n                    \"Deactivation failed\",\n                )\n        except Exception as err:\n            self.logger.critical(\"Database error while deactivating account: %s\", err)\n            return 500, self.err_msg_dic[\"serverinternal\"], DB_ERROR_MSG\n\n    def _handle_contact_update(\n        self, account_name: str, payload: Dict[str, str]\n    ) -> Dict[str, str]:\n        \"\"\"Handle contact update for an account.\"\"\"\n        self.logger.debug(\"Account._handle_contact_update(%s)\", account_name)\n        code, message, detail = self._update_account_contacts(account_name, payload)\n        if code == 200:\n            account_obj = self._lookup_account_by_name(account_name)\n            if account_obj:\n                data = self._build_account_info(account_obj)\n                return self._build_response(code, message, data)\n        return self._build_response(code, message, detail)\n\n    def _update_account_contacts(\n        self, account_name: str, payload: Dict[str, str]\n    ) -> Tuple[int, str, str]:\n        \"\"\"Update account contacts in the database.\"\"\"\n        self.logger.debug(\"Account._update_account_contacts(%s)\", account_name)\n        code, message, detail = self._validate_contact(payload.get(\"contact\", []))\n        if code != 200:\n            return code, message, detail\n\n        try:\n            data_dic = {\"name\": account_name, \"contact\": json.dumps(payload[\"contact\"])}\n            result = self.repository.update_account(data_dic)\n            if result:\n                return 200, None, None\n            else:\n                return 400, self.err_msg_dic[\"accountdoesnotexist\"], \"Update failed\"\n        except Exception as err:\n            self.logger.critical(\n                \"Database error while updating account contacts: %s\", err\n            )\n            return 500, self.err_msg_dic[\"serverinternal\"], DB_ERROR_MSG\n\n    def _handle_key_change(\n        self, account_name: str, payload: Dict[str, str], protected: Dict[str, str]\n    ) -> Dict[str, str]:\n        \"\"\"Handle key change for an account.\"\"\"\n        self.logger.debug(\"Account._handle_key_change(%s)\", account_name)\n        if \"url\" in protected and \"key-change\" in protected[\"url\"]:\n            (\n                code,\n                _message,\n                _detail,\n                inner_protected,\n                inner_payload,\n                _,\n            ) = self.message.check(\n                json.dumps(payload), use_emb_key=True, skip_nonce_check=True\n            )\n            if code == 200:\n                code, message, _detail = self._rollover_account_key(\n                    account_name, protected, inner_protected, inner_payload\n                )\n                if code == 200:\n                    return self._build_response(code, message, None)\n        return self._build_response(\n            400, self.err_msg_dic[\"malformed\"], \"Malformed key-change request\"\n        )\n\n    def _rollover_account_key(\n        self,\n        account_name: str,\n        protected: Dict[str, str],\n        inner_protected: Dict[str, str],\n        inner_payload: Dict[str, str],\n    ) -> Tuple[int, str, str]:\n        \"\"\"Perform key rollover for an account.\"\"\"\n        self.logger.debug(\"Account._rollover_account_key(%s)\", account_name)\n        code, message, detail = self._validate_key_change(\n            account_name, protected, inner_protected, inner_payload\n        )\n        if code == 200:\n            try:\n                data_dic = {\n                    \"name\": account_name,\n                    \"jwk\": json.dumps(inner_protected[\"jwk\"]),\n                }\n                result = self.repository.update_account(data_dic)\n                if result:\n                    return 200, None, None\n                else:\n                    self.logger.error(\n                        \"Key rollover failed for account: %s\", account_name\n                    )\n                    return (\n                        500,\n                        self.err_msg_dic[\"serverinternal\"],\n                        \"Key rollover failed\",\n                    )\n            except Exception as err:\n                self.logger.critical(\n                    \"Database error while updating account key: %s\", err\n                )\n                return 500, self.err_msg_dic[\"serverinternal\"], DB_ERROR_MSG\n        return code, message, detail\n\n    def _validate_key_change(\n        self,\n        account_name: str,\n        protected: Dict[str, str],\n        inner_protected: Dict[str, str],\n        inner_payload: Dict[str, str],\n    ) -> Tuple[int, str, str]:\n        \"\"\"Validate key change request.\"\"\"\n        self.logger.debug(\"Account._validate_key_change(%s)\", account_name)\n        if \"jwk\" not in inner_protected:\n            return 400, self.err_msg_dic[\"malformed\"], \"Inner JWS is missing JWK\"\n\n        key_exists = self._lookup_account_by_field(\n            json.dumps(inner_protected[\"jwk\"]), \"jwk\"\n        )\n        if key_exists:\n            return 400, self.err_msg_dic[\"badpubkey\"], \"Public key already exists\"\n\n        if \"url\" in protected and \"url\" in inner_protected:\n            if protected[\"url\"] != inner_protected[\"url\"]:\n                return (\n                    400,\n                    self.err_msg_dic[\"malformed\"],\n                    \"URL mismatch in inner and outer JWS\",\n                )\n        else:\n            return (\n                400,\n                self.err_msg_dic[\"malformed\"],\n                \"Missing URL in inner or outer JWS\",\n            )\n\n        if \"kid\" in protected and \"account\" in inner_payload:\n            if protected[\"kid\"] != inner_payload[\"account\"]:\n                return (\n                    400,\n                    self.err_msg_dic[\"malformed\"],\n                    \"KID and account do not match\",\n                )\n        else:\n            return (\n                400,\n                self.err_msg_dic[\"malformed\"],\n                \"Missing KID or account in payload\",\n            )\n\n        return 200, None, None\n\n    def _handle_account_query(self, account_name: str) -> Dict[str, str]:\n        \"\"\"Handle account query.\"\"\"\n        self.logger.debug(\"Account._handle_account_query(%s)\", account_name)\n        account_obj = self._lookup_account_by_name(account_name)\n        if account_obj:\n            data = self._build_account_info(account_obj)\n            return self._build_response(200, None, data)\n        return self._build_response(\n            400, self.err_msg_dic[\"accountdoesnotexist\"], \"Account not found\"\n        )\n\n    def _lookup_account_by_name(self, value: str) -> Optional[Dict[str, str]]:\n        \"\"\"Lookup an account in the database.\"\"\"\n        self.logger.debug(\"Account._lookup_account_by_name(name: %s)\", value)\n        try:\n            return self.repository.lookup_account(\"name\", value)\n        except Exception as err:\n            self.logger.critical(\n                \"Database error during account lookup by name: %s\", err\n            )\n            return None\n\n    def _lookup_account_by_field(\n        self, value: str, field: str\n    ) -> Optional[Dict[str, str]]:\n        \"\"\"Lookup account by a specific field.\"\"\"\n        self.logger.debug(\"Account._lookup_account_by_field(%s: %s)\", field, value)\n        try:\n            return self.repository.lookup_account(field, value)\n        except Exception as err:\n            self.logger.critical(\n                \"Database error during account lookup by %s: %s\", field, err\n            )\n            return None\n\n    def _build_account_info(self, account_obj: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"Build account information for response.\"\"\"\n        self.logger.debug(\"Account._build_account_info()\")\n        account_info = {\n            \"status\": account_obj.get(\"status\", \"valid\"),\n            \"key\": json.loads(account_obj[\"jwk\"]),\n            \"contact\": json.loads(account_obj[\"contact\"]),\n            \"createdAt\": date_to_datestr(account_obj[\"created_at\"]),\n        }\n        if \"eab_kid\" in account_obj and account_obj[\"eab_kid\"]:\n            account_info[\"eab_kid\"] = account_obj[\"eab_kid\"]\n\n        self.logger.debug(\n            \"Account._build_account_info() ended with: %s\", bool(account_info)\n        )\n        return account_info\n\n    def _build_response(\n        self,\n        code: int,\n        message: Optional[str],\n        detail: Optional[str],\n        payload: Optional[Dict] = None,\n    ) -> Dict[str, str]:\n        \"\"\"Build a response dictionary.\"\"\"\n        self.logger.debug(\"Account._build_response()\")\n        response_dic = {}\n        if code in (200, 201):\n            response_dic[\"data\"] = {}\n            if code == 201:\n                response_dic[\"data\"] = {\n                    \"status\": \"valid\",\n                    \"orders\": f'{self.server_name}{self.config.path_dic[\"acct_path\"]}{message}/orders',\n                }\n                if payload and \"contact\" in payload:\n                    response_dic[\"data\"][\"contact\"] = payload[\"contact\"]\n            elif code == 200 and detail and \"status\" in detail:\n                response_dic[\"data\"] = detail\n\n            response_dic[\"header\"] = {}\n            response_dic[\"header\"][\n                \"Location\"\n            ] = f'{self.server_name}{self.config.path_dic[\"acct_path\"]}{message}'\n\n            # add exernal account binding\n            if self.config.eab_check and \"externalaccountbinding\" in payload:\n                response_dic[\"data\"][\"externalaccountbinding\"] = payload[\n                    \"externalaccountbinding\"\n                ]\n\n        else:\n            if detail == \"tosfalse\":\n                detail = \"Terms of service must be accepted\"\n\n        # prepare/enrich response\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        response_dic = self.message.prepare_response(response_dic, status_dic)\n\n        return response_dic\n\n    def create_account(self, content: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"Public method to create a new account.\"\"\"\n        self.logger.debug(\"Account.create_account()\")\n        code, message, detail, protected, payload, _ = self.message.check(content, True)\n        if code != 200:\n            return self._build_response(code, message, detail, payload)\n\n        # onlyReturnExisting check\n        if \"onlyreturnexisting\" in payload:\n            code, message, detail = self._onlyreturnexisting(protected, payload)\n        else:\n            code, message, detail = self._create_account(payload, protected)\n\n        self.logger.debug(\"Account.create_account() ended with: %s, %s\", code, message)\n        return self._build_response(code, message, detail, payload)\n\n    def parse_request(self, content: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"Public method to parse an account-related request.\"\"\"\n        self.logger.debug(\"Account.parse_request()\")\n        code, message, detail, protected, payload, account_name = self.message.check(\n            content\n        )\n        if code != 200:\n            return self._build_response(code, message, detail)\n\n        if \"status\" in payload:\n            return self._handle_deactivation(account_name, payload)\n        elif \"contact\" in payload:\n            return self._handle_contact_update(account_name, payload)\n        elif \"payload\" in payload:\n            return self._handle_key_change(account_name, payload, protected)\n        elif not payload:\n            return self._handle_account_query(account_name)\n        else:\n            return self._build_response(\n                400, self.err_msg_dic[\"malformed\"], \"Unknown request\"\n            )\n\n    # Compatibility layer for external methods\n    def new(self, content: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"Compatibility layer for the new method.\"\"\"\n        return self.create_account(content)\n\n    def parse(self, content: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"Compatibility layer for the parse method.\"\"\"\n        return self.parse_request(content)\n"
  },
  {
    "path": "acme_srv/acmechallenge.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"acmechallenge class\"\"\"\nfrom __future__ import print_function\nfrom acme_srv.db_handler import DBstore\n\n\nclass Acmechallenge(object):\n    \"\"\"Acmechallenge handler\"\"\"\n\n    def __init__(self, debug=None, srv_name=None, logger=None):\n        self.server_name = srv_name\n        self.debug = debug\n        self.logger = logger\n        self.dbstore = DBstore(self.debug, self.logger)\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def lookup(self, path_info: str) -> str:\n        \"\"\"check nonce\"\"\"\n        self.logger.debug(\"Acmechallenge.lookup()\")\n\n        key_authorization = None\n        if path_info:\n            token = path_info.replace(\"/.well-known/acme-challenge/\", \"\")\n            self.logger.info(\"Lookup token: %s\", token)\n            challenge_dic = self.dbstore.cahandler_lookup(\"name\", token)\n            if challenge_dic and \"value1\" in challenge_dic:\n                key_authorization = challenge_dic[\"value1\"]\n\n        self.logger.debug(\"Acmechallenge.lookup() ended with: %s\", key_authorization)\n        return key_authorization\n"
  },
  {
    "path": "acme_srv/authorization.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Authorization class - refactored version\"\"\"\n# pylint: disable=R0913, R1705\nfrom __future__ import print_function\nimport json\nfrom typing import List, Tuple, Dict, Optional, Any\nfrom dataclasses import dataclass\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.challenge import Challenge\nfrom acme_srv.helper import (\n    generate_random_string,\n    uts_now,\n    uts_to_date_utc,\n    string_sanitize,\n)\nfrom acme_srv.helpers.config import load_config, config_eab_profile_load\nfrom acme_srv.helpers.domain_utils import is_domain_whitelisted\nfrom acme_srv.message import Message\nfrom acme_srv.nonce import Nonce\n\n\n# Custom Exceptions\nclass AuthorizationError(Exception):\n    \"\"\"Base exception for authorization operations\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\nclass AuthorizationNotFoundError(AuthorizationError):\n    \"\"\"Raised when authorization is not found\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\nclass AuthorizationExpiredError(AuthorizationError):\n    \"\"\"Raised when authorization has expired\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\nclass ConfigurationError(AuthorizationError):\n    \"\"\"Raised when configuration is invalid\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\n@dataclass\nclass AuthorizationConfiguration:\n    \"\"\"Configuration for Authorization operations\"\"\"\n\n    validity: int = 86400\n    expiry_check_disable: bool = False\n    authz_path: str = \"/acme/authz/\"\n    prevalidated_domainlist: Optional[List[str]] = None\n    eab_profiling: bool = False\n    eab_handler: Optional[Any] = None\n\n\n@dataclass\nclass AuthorizationData:\n    \"\"\"Authorization data structure\"\"\"\n\n    name: str\n    status: str\n    expires: int\n    token: str\n    identifier: Optional[Dict[str, str]] = None\n    challenges: Optional[List[Dict[str, str]]] = None\n    wildcard: bool = False\n\n    def to_dict(self) -> Dict[str, str]:\n        \"\"\"Convert to dictionary for response\"\"\"\n        result = {\n            \"status\": self.status,\n            \"expires\": uts_to_date_utc(self.expires),\n        }\n\n        if self.identifier:\n            result[\"identifier\"] = self.identifier\n\n        if self.wildcard:\n            result[\"wildcard\"] = self.wildcard\n\n        if self.challenges:\n            result[\"challenges\"] = self.challenges\n\n        return result\n\n\nclass AuthorizationRepository:\n    \"\"\"Repository class for authorization database operations\"\"\"\n\n    def __init__(self, dbstore: DBstore, logger):\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def find_authorization_by_name(\n        self, authz_name: str, field_list: List[str] = None\n    ) -> Optional[Dict[str, str]]:\n        \"\"\"Find authorization by name in database\"\"\"\n        self.logger.debug(\n            \"AuthorizationRepository.find_authorization_by_name(%s)\", authz_name\n        )\n\n        try:\n            if field_list:\n                authz_list = self.dbstore.authorization_lookup(\n                    \"name\", authz_name, field_list\n                )\n            else:\n                authz_list = self.dbstore.authorization_lookup(\"name\", authz_name)\n\n            # authorization_lookup returns a list, we want the first item if it exists\n            if authz_list and len(authz_list) > 0:\n                return authz_list[0]\n            else:\n                return None\n\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to lookup authorization '%s': %s\",\n                authz_name,\n                err,\n            )\n            raise AuthorizationError(\n                f\"Failed to find authorization '{authz_name}': {err}\"\n            ) from err\n\n    def update_authorization_expiry(\n        self, authz_name: str, token: str, expires: int\n    ) -> None:\n        \"\"\"Update authorization expiry date and token\"\"\"\n        self.logger.debug(\n            \"AuthorizationRepository.update_authorization_expiry(%s)\", authz_name\n        )\n\n        try:\n            self.dbstore.authorization_update(\n                {\"name\": authz_name, \"token\": token, \"expires\": expires}\n            )\n        except Exception as err:\n            self.logger.error(\n                \"Database error during authorization update (%s): %s\", authz_name, err\n            )\n            raise AuthorizationError(\n                f\"Failed to update authorization '{authz_name}': {err}\"\n            ) from err\n\n    def search_expired_authorizations(\n        self, timestamp: int, field_list: List[str]\n    ) -> List[Dict[str, str]]:\n        \"\"\"Search for expired authorizations\"\"\"\n        self.logger.debug(\n            \"AuthorizationRepository.search_expired_authorizations(%s)\", timestamp\n        )\n\n        try:\n            return self.dbstore.authorizations_expired_search(\n                \"expires\", timestamp, vlist=field_list, operant=\"<=\"\n            )\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to search for expired authorizations: %s\", err\n            )\n            raise AuthorizationError(\n                f\"Failed to search expired authorizations: {err}\"\n            ) from err\n\n    def mark_authorization_as_expired(self, authz_name: str) -> None:\n        \"\"\"Mark authorization as expired\"\"\"\n        self.logger.debug(\n            \"AuthorizationRepository.mark_authorization_as_expired(%s)\", authz_name\n        )\n\n        try:\n            self.dbstore.authorization_update({\"name\": authz_name, \"status\": \"expired\"})\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to update authorization '%s' as expired: %s\",\n                authz_name,\n                err,\n            )\n            raise AuthorizationError(\n                f\"Failed to expire authorization '{authz_name}': {err}\"\n            ) from err\n\n    def mark_authorization_as_valid(self, authz_name: str) -> None:\n        \"\"\"Mark authorization as valid\"\"\"\n        self.logger.debug(\n            \"AuthorizationRepository.mark_authorization_as_valid(%s)\", authz_name\n        )\n\n        try:\n            self.dbstore.authorization_update({\"name\": authz_name, \"status\": \"valid\"})\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to update authorization '%s' as valid: %s\",\n                authz_name,\n                err,\n            )\n            raise AuthorizationError(\n                f\"Failed to mark authorization '{authz_name}' as valid: {err}\"\n            ) from err\n\n    def mark_order_as_ready(self, order_name: str) -> None:\n        \"\"\"Mark order as ready\"\"\"\n        self.logger.debug(\"AuthorizationRepository.mark_order_as_ready(%s)\", order_name)\n\n        try:\n            self.dbstore.order_update({\"name\": order_name, \"status\": \"ready\"})\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to update order '%s' as valid: %s\",\n                order_name,\n                err,\n            )\n            raise AuthorizationError(\n                f\"Failed to mark order '{order_name}' as valid: {err}\"\n            ) from err\n\n\nclass AuthorizationBusinessLogic:\n    \"\"\"Business logic for authorization operations\"\"\"\n\n    def __init__(\n        self,\n        config: AuthorizationConfiguration,\n        repository: AuthorizationRepository,\n        logger,\n    ):\n        self.config = config\n        self.repository = repository\n        self.logger = logger\n\n    def extract_authorization_name_from_url(self, url: str, server_name: str) -> str:\n        \"\"\"Extract authorization name from URL\"\"\"\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.extract_authorization_name_from_url()\"\n        )\n\n        authz_name = string_sanitize(\n            self.logger, url.replace(f\"{server_name}{self.config.authz_path}\", \"\")\n        )\n        return authz_name\n\n    def generate_authorization_token_and_expiry(self) -> Tuple[str, int]:\n        \"\"\"Generate new token and expiry time\"\"\"\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.generate_authorization_token_and_expiry()\"\n        )\n\n        expires = uts_now() + self.config.validity\n        token = generate_random_string(self.logger, 32)\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.generate_authorization_token_and_expiry() ended: Generated token expires at: %s\",\n            expires,\n        )\n        return token, expires\n\n    def enrich_authorization_with_identifier_info(\n        self, auth_db_info: Dict[str, str]\n    ) -> Tuple[Dict[str, str], bool]:\n        \"\"\"Extract and enrich authorization with identifier information\"\"\"\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.enrich_authorization_with_identifier_info()\"\n        )\n\n        if not auth_db_info:\n            return {}, False\n\n        auth_info = auth_db_info[0] if isinstance(auth_db_info, list) else auth_db_info\n        identifier_info = {}\n        is_tnauth = False\n\n        # Extract status\n        status = auth_info.get(\"status__name\", \"pending\")\n        identifier_info[\"status\"] = status\n\n        # Extract identifier\n        if \"type\" in auth_info and \"value\" in auth_info:\n            identifier_info[\"identifier\"] = {\n                \"type\": auth_info[\"type\"],\n                \"value\": auth_info[\"value\"],\n            }\n\n            # Check for TNAuthList\n            if auth_info[\"type\"] == \"TNAuthList\":\n                is_tnauth = True\n\n            # Handle wildcard domains\n            if auth_info[\"value\"].startswith(\"*.\"):\n                self.logger.debug(\"Adding wildcard flag to authorization\")\n                identifier_info[\"identifier\"][\"value\"] = auth_info[\"value\"][2:]\n                identifier_info[\"wildcard\"] = True\n\n        return identifier_info, is_tnauth\n\n    def extract_identifier_info_for_challenge(\n        self, authz_info_dict: Dict[str, str]\n    ) -> Tuple[str, str]:\n        \"\"\"Extract identifier type and value for challenge operations\"\"\"\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.extract_identifier_info_for_challenge()\"\n        )\n\n        if \"identifier\" not in authz_info_dict:\n            return None, None\n\n        identifier = authz_info_dict[\"identifier\"]\n        id_type = identifier.get(\"type\")\n        id_value = identifier.get(\"value\")\n\n        return id_type, id_value\n\n    def is_authorization_eligible_for_expiry(self, auth_record: Dict[str, str]) -> bool:\n        \"\"\"Check if authorization should be expired\"\"\"\n        self.logger.debug(\n            \"AuthorizationBusinessLogic.is_authorization_eligible_for_expiry()\"\n        )\n\n        # Must have name and status\n        if \"name\" not in auth_record or \"status__name\" not in auth_record:\n            return False\n\n        # Skip if already expired\n        if auth_record[\"status__name\"] == \"expired\":\n            return False\n\n        # Skip corner cases where expiry is set to 0\n        if \"expires\" in auth_record and auth_record[\"expires\"] == 0:\n            return False\n\n        return True\n\n\nclass ChallengeSetManager:\n    \"\"\"Manager for challenge set operations\"\"\"\n\n    def __init__(self, debug: bool, server_name: str, logger):\n        self.debug = debug\n        self.server_name = server_name\n        self.logger = logger\n\n    def get_challenge_set_for_authorization(\n        self,\n        authz_name: str,\n        status: str,\n        token: str,\n        is_tnauth: bool,\n        expires: int,\n        id_type: str = None,\n        id_value: str = None,\n    ) -> List[Dict[str, str]]:\n        \"\"\"Get challenge set for authorization\"\"\"\n        self.logger.debug(\n            \"ChallengeSetManager.get_challenge_set_for_authorization(%s)\", authz_name\n        )\n\n        with Challenge(\n            debug=self.debug,\n            srv_name=self.server_name,\n            logger=self.logger,\n            expiry=expires,\n        ) as challenge:\n            return challenge.challengeset_get(\n                authz_name, status, token, is_tnauth, id_type, id_value\n            )\n\n\nclass Authorization(object):\n    \"\"\"Refactored Authorization class with clear separation of concerns\"\"\"\n\n    def __init__(\n        self, debug: bool = False, srv_name: str = None, logger: object = None\n    ):\n        self.server_name = srv_name\n        self.debug = debug\n        self.logger = logger\n\n        # Initialize dependencies\n        self.dbstore = DBstore(debug, self.logger)\n        self.message = Message(debug, self.server_name, self.logger)\n        self.nonce = Nonce(debug, self.logger)\n\n        # Initialize components immediately\n        self.config = AuthorizationConfiguration()\n        self.repository = AuthorizationRepository(self.dbstore, self.logger)\n        self.business_logic = AuthorizationBusinessLogic(\n            self.config, self.repository, self.logger\n        )\n        self.challenge_manager = ChallengeSetManager(\n            self.debug, self.server_name, self.logger\n        )\n\n    def __enter__(self):\n        \"\"\"Makes Authorization a Context Manager\"\"\"\n        self._load_configuration()\n        # Re-initialize business logic with updated config\n        self.business_logic = AuthorizationBusinessLogic(\n            self.config, self.repository, self.logger\n        )\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Close the connection at the end of the context\"\"\"\n        # pylint: disable=unnecessary-pass\n        pass\n\n    def _load_configuration(self) -> AuthorizationConfiguration:\n        \"\"\"Load configuration from file\"\"\"\n        self.logger.debug(\"Authorization._load_configuration()\")\n\n        config_dic = load_config()\n\n        if config_dic:\n\n            try:\n                self.config.validity = int(\n                    config_dic.get(\"Authorization\", \"validity\", fallback=86400)\n                )\n            except ValueError as err:\n                raise ConfigurationError(\n                    f\"Invalid validity parameter: {config_dic.get('Authorization', 'validity')}\"\n                ) from err\n\n            self.config.expiry_check_disable = config_dic.getboolean(\n                \"Authorization\", \"expiry_check_disable\", fallback=False\n            )\n            url_prefix = config_dic.get(\"Directory\", \"url_prefix\", fallback=None)\n            if url_prefix:\n                self.config.authz_path = f\"{url_prefix}{self.config.authz_path}\"\n\n            try:\n                # load  prevalidated_domainlist\n                self.config.prevalidated_domainlist = json.loads(\n                    config_dic.get(\n                        \"Authorization\", \"prevalidated_domainlist\", fallback=\"null\"\n                    )\n                )\n                if self.config.prevalidated_domainlist:\n                    self.logger.warning(\n                        \"Prevalidated list of domains loaded globally. Such configuration is NOT recommended as this is a severe security risk!\"\n                    )\n            except json.JSONDecodeError as err:\n                self.config.prevalidated_domainlist = None\n                raise ConfigurationError(\n                    \"Invalid prevalidated_domainlist parameter\"\n                ) from err\n\n            # load profiling\n            (\n                self.config.eab_profiling,\n                self.config.eab_handler,\n            ) = config_eab_profile_load(self.logger, config_dic)\n\n        self.logger.debug(\"Authorization._load_configuration() ended:\")\n\n    def get_authorization_details(self, url: str) -> Optional[Dict[str, str]]:\n        \"\"\"Get detailed authorization information\"\"\"\n        self.logger.debug(\"Authorization.get_authorization_details()\")\n\n        # Extract authorization name from URL\n        authz_name = self.business_logic.extract_authorization_name_from_url(\n            url, self.server_name\n        )\n        self.logger.debug(\"Authorization name: %s\", authz_name)\n\n        # Check if authorization exists\n        authz = self.repository.find_authorization_by_name(authz_name)\n        if not authz:\n            self.logger.debug(\"Authorization not found: %s\", authz_name)\n            return {}\n\n        # Generate new token and expiry\n        token, expires = self.business_logic.generate_authorization_token_and_expiry()\n        # Update authorization with new expiry and token (if there is no token yet)\n        self.repository.update_authorization_expiry(authz_name, token, expires)\n\n        # Create base authorization info\n        authz_info = {\n            \"expires\": uts_to_date_utc(expires),\n        }\n\n        # Get detailed authorization information\n        auth_details = self.repository.find_authorization_by_name(\n            authz_name,\n            [\n                \"status__name\",\n                \"type\",\n                \"value\",\n                \"order__name\",\n                \"order__account__name\",\n                \"order__account__eab_kid\",\n            ],\n        )\n\n        if auth_details:\n            (\n                identifier_info,\n                is_tnauth,\n            ) = self.business_logic.enrich_authorization_with_identifier_info(\n                auth_details\n            )\n            authz_info.update(identifier_info)\n        else:\n            authz_info[\"status\"] = \"pending\"\n            is_tnauth = False\n\n        # Extract identifier type and value\n        id_type, id_value = self.business_logic.extract_identifier_info_for_challenge(\n            authz_info\n        )\n\n        if auth_details:\n            # Apply EAB profile and domain whitelist logic\n            self._apply_eab_and_domain_whitelist(\n                authz_name, auth_details, id_type, id_value, authz_info\n            )\n\n        # Get challenge set\n        try:\n            authz_info[\n                \"challenges\"\n            ] = self.challenge_manager.get_challenge_set_for_authorization(\n                authz_name,\n                authz_info[\"status\"],\n                token,\n                is_tnauth,\n                expires,\n                id_type,\n                id_value,\n            )\n        except Exception as err:\n            self.logger.error(\n                \"Failed to create challenge set for authorization %s: %s\",\n                authz_name,\n                err,\n            )\n            return None\n\n        self.logger.debug(\n            \"Authorization.get_authorization_details() returns: %s\",\n            json.dumps(authz_info),\n        )\n        return authz_info\n\n    def _apply_eab_and_domain_whitelist(\n        self, authz_name, auth_details, id_type, id_value, authz_info\n    ):\n        \"\"\"Apply EAB profile settings and domain whitelist logic to authorization info.\"\"\"\n        self._apply_eab_profile(authz_name, auth_details)\n        self._apply_domain_whitelist(\n            authz_name, auth_details, id_type, id_value, authz_info\n        )\n\n    def _apply_eab_profile(self, authz_name, auth_details):\n        if not self.config.eab_profiling:\n            return\n        self.logger.debug(\n            \"Authorization._apply_eab_and_domain_whitelist() - apply eab profile setting\"\n        )\n        eab_kid = auth_details.get(\"order__account__eab_kid\") if auth_details else None\n        if not eab_kid:\n            return\n        try:\n            with self.config.eab_handler(self.logger) as eab_handler:\n                profile_dic = eab_handler.key_file_load()\n                prevalidated_domainlist = (\n                    profile_dic.get(eab_kid, {})\n                    .get(\"authorization\", {})\n                    .get(\"prevalidated_domainlist\")\n                )\n                if prevalidated_domainlist:\n                    self.logger.debug(\n                        \"Authorization._apply_eab_and_domain_whitelist() - apply prevalidated_domainlist from eab profile.\"\n                    )\n                    self.config.prevalidated_domainlist = prevalidated_domainlist\n        except Exception as err:\n            self.logger.error(\n                \"Failed to process EAB profile for challenge %s (kid: %s): %s\",\n                authz_name,\n                eab_kid,\n                err,\n            )\n\n    def _apply_domain_whitelist(\n        self, authz_name, auth_details, id_type, id_value, authz_info\n    ):\n        if id_type != \"dns\" or not getattr(\n            self.config, \"prevalidated_domainlist\", None\n        ):\n            return\n        self.logger.debug(\n            \"Authorization.get_authorization_details() - Checking preauthorized domain list for DNS identifier\"\n        )\n        if is_domain_whitelisted(\n            self.logger, id_value, self.config.prevalidated_domainlist\n        ):\n            self.logger.debug(\n                \"Domain %s is preauthorized, setting authorization status to 'valid'\",\n                id_value,\n            )\n            authz_info[\"status\"] = \"valid\"\n            self.repository.mark_authorization_as_valid(authz_name)\n            if auth_details is not None:\n                self.repository.mark_order_as_ready(auth_details.get(\"order__name\"))\n            else:\n                self.logger.debug(\n                    \"No order information found for authorization %s\", authz_name\n                )\n\n    def expire_invalid_authorizations(\n        self, timestamp: int = None\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"Expire invalid authorizations\"\"\"\n        self.logger.debug(\"Authorization.expire_invalid_authorizations(%s)\", timestamp)\n\n        if timestamp is None:\n            timestamp = uts_now()\n            self.logger.debug(\"Set timestamp to current time: %s\", timestamp)\n\n        field_list = [\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"value\",\n            \"created_at\",\n            \"token\",\n            \"status__id\",\n            \"status__name\",\n            \"order__id\",\n            \"order__name\",\n        ]\n\n        try:\n            # Search for expired authorizations\n            expired_authz_list = self.repository.search_expired_authorizations(\n                timestamp, field_list\n            )\n        except AuthorizationError as err:\n            self.logger.warning(\"Failed to search for expired authorizations: %s\", err)\n            return field_list, []\n\n        # Process expired authorizations\n        expired_output = []\n        for authz_record in expired_authz_list:\n            try:\n                if self.business_logic.is_authorization_eligible_for_expiry(\n                    authz_record\n                ):\n                    expired_output.append(authz_record)\n                    self.repository.mark_authorization_as_expired(authz_record[\"name\"])\n            except AuthorizationError as err:\n                self.logger.warning(\n                    \"Failed to expire authorization %s: %s\",\n                    authz_record.get(\"name\"),\n                    err,\n                )\n                # Continue processing other authorizations\n                continue\n\n        self.logger.debug(\n            \"Authorization.expire_invalid_authorizations() ended: %s authorizations expired\",\n            len(expired_output),\n        )\n        return field_list, expired_output\n\n    def handle_get_request(self, url: str) -> Dict[str, str]:\n        \"\"\"Handle GET request for authorization\"\"\"\n        self.logger.debug(\"Authorization.handle_get_request()\")\n\n        try:\n            authorization_data = self.get_authorization_details(url)\n            if authorization_data:\n                return {\"code\": 200, \"header\": {}, \"data\": authorization_data}\n            else:\n                return {\n                    \"code\": 404,\n                    \"header\": {},\n                    \"data\": {\"error\": \"Authorization not found\"},\n                }\n        except AuthorizationError as err:\n            self.logger.error(\"Authorization error: %s\", err)\n            return {\"code\": 404, \"header\": {}, \"data\": {\"error\": str(err)}}\n\n    def handle_post_request(self, content: str) -> Dict[str, str]:\n        \"\"\"Handle POST request for authorization\"\"\"\n        self.logger.debug(\"Authorization.handle_post_request()\")\n\n        # Expire invalid authorizations if not disabled\n        if not self.config.expiry_check_disable:\n            try:\n                self.invalidate()  # Call public method for backward compatibility\n            except Exception as err:\n                self.logger.warning(\"Failed to expire authorizations: %s\", err)\n                # Continue with processing - don't fail the request\n\n        # Validate message\n        code, message, detail, protected, _payload, _account_name = self.message.check(\n            content\n        )\n\n        response_dic = {}\n        if code == 200:\n            if \"url\" not in protected:\n                code = 400\n                message = \"urn:ietf:params:acme:error:malformed\"\n                detail = \"url is missing in protected\"\n            else:\n                try:\n                    auth_info = self.get_authorization_details(protected[\"url\"])\n                    if auth_info:\n                        response_dic[\"data\"] = auth_info\n                    else:\n                        code = 403\n                        message = \"urn:ietf:params:acme:error:unauthorized\"\n                        detail = \"authorization lookup failed\"\n                except AuthorizationError as err:\n                    self.logger.error(\"Authorization error: %s\", err)\n                    code = 403\n                    message = \"urn:ietf:params:acme:error:unauthorized\"\n                    detail = \"authorization error\"\n\n        # Prepare response\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        response_dic = self.message.prepare_response(response_dic, status_dic)\n\n        self.logger.debug(\n            \"Authorization.handle_post_request() returns: %s\", json.dumps(response_dic)\n        )\n        return response_dic\n\n    # Backward compatibility methods (delegating to new methods)\n    def new_get(self, url: str) -> Dict[str, str]:\n        \"\"\"Backward compatibility: handle GET request\"\"\"\n        self.logger.debug(\"Authorization.new_get()\")\n        return self.handle_get_request(url)\n\n    def new_post(self, content: str) -> Dict[str, str]:\n        \"\"\"Backward compatibility: handle POST request\"\"\"\n        self.logger.debug(\"Authorization.new_post()\")\n        return self.handle_post_request(content)\n\n    def invalidate(self, timestamp: int = None) -> Tuple[List[str], List[str]]:\n        \"\"\"Backward compatibility: expire invalid authorizations\"\"\"\n        self.logger.debug(\"Authorization.invalidate()\")\n        return self.expire_invalid_authorizations(timestamp)\n"
  },
  {
    "path": "acme_srv/certificate.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=r0902, r0912, r0913, r0915, r1705\n\"\"\"certificate class\"\"\"\nfrom __future__ import print_function\nimport json\nfrom typing import List, Tuple, Dict, Union, Optional, Any\nfrom dataclasses import dataclass\nfrom acme_srv.helper import (\n    b64_url_recode,\n    ca_handler_load,\n    cert_aki_get,\n    cert_cn_get,\n    cert_dates_get,\n    cert_extensions_get,\n    cert_san_get,\n    cert_serial_get,\n    certid_asn1_get,\n    csr_san_get,\n    csr_extensions_get,\n    date_to_uts_utc,\n    error_dic_get,\n    hooks_load,\n    load_config,\n    pembundle_to_list,\n    string_sanitize,\n    uts_now,\n    uts_to_date_utc,\n    config_async_mode_load,\n)\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.message import Message\nfrom acme_srv.threadwithreturnvalue import ThreadWithReturnValue\nfrom acme_srv.certificate_manager import CertificateManager\nfrom acme_srv.certificate_repository import DatabaseCertificateRepository\n\n\n# CertificateLogger moved from certificate_logger.py\nclass CertificateLogger:\n    \"\"\"Handles all certificate operation logging\"\"\"\n\n    def __init__(self, logger, cert_operations_log: str, repository):\n        \"\"\"\n        Initialize certificate logger\n\n        Args:\n            logger: Logger instance for output\n            cert_operations_log: Logging format (\"json\", \"text\", or \"\")\n            repository: Repository for database operations\n        \"\"\"\n        self.logger = logger\n        self.cert_operations_log = cert_operations_log\n        self.repository = repository\n\n    def log_certificate_issuance(\n        self,\n        certificate_name: str,\n        certificate: str,\n        order_name: str,\n        cert_reusage: bool = False,\n    ):\n        \"\"\"Log certificate issuance\"\"\"\n        self.logger.debug(\n            \"CertificateLogger.log_certificate_issuance(%s)\", certificate_name\n        )\n\n        # Lookup account name and kid\n        try:\n            order_dic = self.repository.order_lookup(\n                \"name\",\n                order_name,\n                [\n                    \"id\",\n                    \"name\",\n                    \"account__name\",\n                    \"account__eab_kid\",\n                    \"profile\",\n                    \"expires\",\n                    \"account__contact\",\n                ],\n            )\n        except Exception as err:\n            self.logger.error(\n                \"Database error: failed to get account information for cert issuance log: %s\",\n                err,\n            )\n            order_dic = {}\n\n        data_dic = {\n            \"account_name\": order_dic.get(\"account__name\", \"\"),\n            \"account_contact\": order_dic.get(\"account__contact\", \"\"),\n            \"certificate_name\": certificate_name,\n            \"serial_number\": cert_serial_get(self.logger, certificate, hexformat=True),\n            \"common_name\": cert_cn_get(self.logger, certificate),\n            \"san_list\": cert_san_get(self.logger, certificate),\n        }\n\n        if cert_reusage:\n            # Add cert reusage flag if set to true\n            data_dic[\"reused\"] = cert_reusage\n\n        if order_dic.get(\"account__eab_kid\", \"\"):\n            # Add kid if existing\n            data_dic[\"eab_kid\"] = order_dic.get(\"account__eab_kid\", \"\")\n\n        if order_dic.get(\"profile\", None):\n            # Add profile if existing\n            data_dic[\"profile\"] = order_dic.get(\"profile\", \"\")\n\n        if order_dic.get(\"expires\", \"\"):\n            # add expires if existing\n            data_dic[\"expires\"] = uts_to_date_utc(order_dic.get(\"expires\", \"\"))\n\n        if self.cert_operations_log == \"json\":\n            # Log in json format\n            self._log_as_json(data_dic, \"Certificate issued\")\n        else:\n            # Log in text format\n            self._log_issuance_as_text(certificate_name, data_dic)\n\n        self.logger.debug(\"CertificateLogger.log_certificate_issuance() ended\")\n\n    def log_certificate_revocation(self, certificate: str, code: int):\n        \"\"\"Log certificate revocation\"\"\"\n        self.logger.debug(\"CertificateLogger.log_certificate_revocation()\")\n\n        if code == 200:\n            status = \"successful\"\n        else:\n            status = \"failed\"\n\n        # Lookup account name and kid\n        try:\n            cert_dic = self.repository.certificate_lookup(\n                \"cert_raw\",\n                b64_url_recode(self.logger, certificate),\n                [\n                    \"name\",\n                    \"order__account__name\",\n                    \"order__account__eab_kid\",\n                    \"order__account__contact\",\n                    \"order__profile\",\n                ],\n            )\n        except Exception as err:\n            self.logger.error(\n                \"Database error: failed to get account information for cert revocation: %s\",\n                err,\n            )\n            cert_dic = {}\n\n        # Construct log message including certificate name\n        self.logger.debug(\n            \"CertificateLogger.log_certificate_revocation(%s)\", cert_dic.get(\"name\", \"\")\n        )\n\n        data_dic = {\n            \"account_name\": cert_dic.get(\"order__account__name\", \"\"),\n            \"account_contact\": cert_dic.get(\"order__account__contact\", \"\"),\n            \"certificate_name\": cert_dic.get(\"name\", \"\"),\n            \"serial_number\": cert_serial_get(self.logger, certificate, hexformat=True),\n            \"common_name\": cert_cn_get(self.logger, certificate),\n            \"profile\": cert_dic.get(\"order__profile\", \"\"),\n            \"san_list\": cert_san_get(self.logger, certificate),\n            \"status\": status,\n        }\n\n        if cert_dic.get(\"order__account__eab_kid\", \"\"):\n            data_dic[\"eab_kid\"] = cert_dic.get(\"order__account__eab_kid\")\n\n        if self.cert_operations_log == \"json\":\n            # Log in json format\n            self._log_as_json(data_dic, \"Certificate revoked\")\n        else:\n            # Log in text format\n            self._log_revocation_as_text(data_dic)\n\n        self.logger.debug(\"CertificateLogger.log_certificate_revocation() ended\")\n\n    def _log_as_json(self, data_dic: Dict, operation_type: str):\n        \"\"\"Log data as JSON format\"\"\"\n        self.logger.info(\n            \"%s: %s\",\n            operation_type,\n            json.dumps(data_dic, sort_keys=True),\n        )\n\n    def _log_issuance_as_text(self, certificate_name: str, data_dic: Dict):\n        \"\"\"Log certificate issuance as text string\"\"\"\n        log_string = f'Certificate {certificate_name} issued for account {data_dic[\"account_name\"]} {data_dic[\"account_contact\"]}'\n\n        if data_dic.get(\"eab_kid\", \"\"):\n            log_string = log_string + f' with EAB KID {data_dic[\"eab_kid\"]}'\n\n        if data_dic.get(\"profile\", \"\"):\n            log_string = log_string + f' with Profile {data_dic[\"profile\"]}'\n\n        log_string = (\n            log_string\n            + f', Serial: {data_dic[\"serial_number\"]}, Common Name: {data_dic[\"common_name\"]}, SANs: {data_dic[\"san_list\"]}, Expires: {data_dic[\"expires\"]}'\n        )\n\n        if data_dic.get(\"reused\", \"\"):\n            log_string = log_string + f' reused: {data_dic[\"reused\"]}'\n\n        self.logger.info(log_string)\n\n    def _log_revocation_as_text(self, data_dic: Dict):\n        \"\"\"Log certificate revocation as text string\"\"\"\n        log_string = f'Certificate {data_dic[\"certificate_name\"]} revocation {data_dic[\"status\"]} for account {data_dic[\"account_name\"]} {data_dic[\"account_contact\"]}'  # noqa: E501\n\n        if data_dic.get(\"eab_kid\", \"\"):\n            log_string = log_string + f' with EAB KID {data_dic[\"eab_kid\"]}'\n\n        if data_dic.get(\"profile\", \"\"):\n            log_string = log_string + f' with Profile {data_dic[\"profile\"]}'\n\n        log_string = (\n            log_string\n            + f'. Serial: {data_dic[\"serial_number\"]}, Common Name: {data_dic[\"common_name\"]}, SANs: {data_dic[\"san_list\"]}'\n        )\n\n        self.logger.info(log_string)\n\n\n# CertificateConfiguration harmonized with Challenge class approach\n@dataclass\nclass CertificateConfiguration:\n    \"\"\"\n    Configuration dataclass for Certificate operations.\n    Centralizes all configuration settings for the Certificate class and its components.\n    \"\"\"\n\n    debug: bool = False\n    server_name: Optional[str] = None\n    cert_operations_log: Optional[Any] = None\n    cert_reusage_timeframe: int = 0\n    cn2san_add: bool = False\n    enrollment_timeout: int = 5\n    retry_after: int = 600\n    tnauthlist_support: bool = False\n    async_mode: bool = False\n    ignore_pre_hook_failure: bool = False\n    ignore_post_hook_failure: bool = True\n    ignore_success_hook_failure: bool = False\n\n\nclass Certificate(object):\n    \"\"\"CA  handler\"\"\"\n\n    # Order status constants for better readability\n    ORDER_STATUS_PROCESSING = 4\n    ORDER_STATUS_VALID = 5\n\n    # Error message constants\n    INVALID_INPUT_PARAMS_MSG = \"Invalid input parameters: %s\"\n\n    def __init__(self, debug: bool = False, srv_name: str = None, logger=None):\n        self.debug = debug\n        self.logger = logger\n        self.server_name = srv_name\n\n        self.path_dic = {\"cert_path\": \"/acme/cert/\"}\n\n        # Create configuration dataclass from config file using harmonized approach\n        self.config = CertificateConfiguration()\n\n        # Core components\n        self.dbstore = DBstore(self.debug, self.logger)\n        self.repository = DatabaseCertificateRepository(self.dbstore, self.logger)\n\n        # Legacy properties for backward compatibility\n        self.cahandler = None\n        self.err_msg_dic = error_dic_get(self.logger)\n        self.hooks = None\n        self.message = Message(self.debug, self.server_name, self.logger)\n\n        # Initialize the new architecture components with configuration\n        self.certificate_manager = CertificateManager(\n            self.debug, self.logger, self.err_msg_dic, self.repository, self.config\n        )\n        self.certificate_logger = CertificateLogger(\n            self.logger, self.config.cert_operations_log, self.repository\n        )\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _validate_input_parameters(self, **kwargs) -> Dict[str, str]:\n        \"\"\"Validate input parameters and return validation errors\"\"\"\n        errors = {}\n        for param_name, param_value in kwargs.items():\n            if param_value is None or (\n                isinstance(param_value, str) and not param_value.strip()\n            ):\n                errors[param_name] = f\"{param_name} cannot be empty or None\"\n        return errors\n\n    def _create_error_response(\n        self, code: int, message: str, detail: str = None\n    ) -> Dict[str, str]:\n        \"\"\"Create standardized error response\"\"\"\n        return {\"code\": code, \"data\": message, \"detail\": detail}\n\n    def _validate_certificate_account_ownership(\n        self, account_name: str, certificate: str\n    ) -> bool:\n        \"\"\"Validate that the account owns the certificate\"\"\"\n        self.logger.debug(\"Certificate._validate_certificate_account_ownership()\")\n        try:\n            result = self.repository.certificate_account_check(\n                account_name, b64_url_recode(self.logger, certificate)\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to check account for certificate: %s\", err_\n            )\n            result = None\n        self.logger.debug(\n            \"Certificate._validate_certificate_account_ownership() ended with: %s\",\n            result,\n        )\n        return result\n\n    def _validate_certificate_authorization(\n        self, identifier_dic: Dict[str, str], certificate: str\n    ) -> List[str]:\n        self.logger.debug(\"Certificate._validate_certificate_authorization()\")\n        # load identifiers\n        try:\n            identifiers = json.loads(identifier_dic[\"identifiers\"].lower())\n        except Exception:\n            identifiers = []\n\n        # check if we have a tnauthlist identifier\n        tnauthlist_identifer_in = self._check_for_tnauth_identifiers(identifiers)\n        if self.config.tnauthlist_support and tnauthlist_identifer_in:\n            try:\n                # get list of certextensions in base64 format and identifier status\n                tnauthlist = cert_extensions_get(self.logger, certificate)\n                identifier_status = self._validate_identifiers_against_tnauthlist(\n                    identifier_dic, tnauthlist\n                )\n            except Exception as err:\n                # enough to set identifier_list as empty list\n                identifier_status = []\n                self.logger.warning(\n                    \"Error while parsing certificate for TNAuthList identifier check: %s\",\n                    err,\n                )\n        else:\n            try:\n                # get sans\n                san_list = cert_san_get(self.logger, certificate)\n                if self.config.cn2san_add:\n                    # add common name to SANs\n                    cert_cn = cert_cn_get(self.logger, certificate)\n                    if not san_list and cert_cn:\n                        san_list.append(f\"DNS:{cert_cn}\")\n\n                identifier_status = self._validate_identifiers_against_sans(\n                    identifiers, san_list\n                )\n            except Exception as err:\n                # enough to set identifier_list as empty list\n                identifier_status = []\n                self.logger.warning(\n                    \"Error while parsing certificate for SAN identifier check: %s\",\n                    err,\n                )\n\n        self.logger.debug(\"Certificate._validate_certificate_authorization() ended\")\n        return identifier_status\n\n    def _validate_order_authorization(self, order_name: str, certificate: str) -> bool:\n        \"\"\"Validate that the account holds authorization for all identifiers = SANs in the certificate\"\"\"\n        self.logger.debug(\"Certificate._validate_order_authorization()\")\n\n        # empty list of statuses\n        identifier_status = []\n\n        # get identifiers for order\n        try:\n            identifier_dic = self.repository.order_lookup(\n                \"name\", order_name, [\"identifiers\"]\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to check authorization for order '%s': %s\",\n                order_name,\n                err_,\n            )\n            identifier_dic = {}\n\n        if identifier_dic and \"identifiers\" in identifier_dic:\n            # get identifier status list\n            identifier_status = self._validate_certificate_authorization(\n                identifier_dic, certificate\n            )\n\n        result = False\n        if identifier_status and False not in identifier_status:\n            result = True\n\n        self.logger.debug(\n            \"Certificate._validate_order_authorization() ended with %s\", result\n        )\n        return result\n\n    def _check_certificate_reusability(self, csr: str) -> Tuple[None, str, str, str]:\n        \"\"\"Check if an existing certificate can be reused\"\"\"\n        self.logger.debug(\n            \"Certificate._check_certificate_reusability(%s)\",\n            self.config.cert_reusage_timeframe,\n        )\n        try:\n            result_dic = self.repository.search_certificates(\n                \"csr\",\n                csr,\n                (\"cert\", \"cert_raw\", \"expire_uts\", \"issue_uts\", \"created_at\", \"id\"),\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to search for certificate reusage: %s\", err_\n            )\n            result_dic = None\n\n        cert = None\n        cert_raw = None\n        message = None\n\n        if result_dic:\n            self.logger.debug(\n                \"Certificate._check_certificate_reusability(): found %s certificates\",\n                len(result_dic),\n            )\n            uts = uts_now()\n            # sort certificates by creation date\n            for certificate in sorted(\n                result_dic, key=lambda i: i[\"issue_uts\"], reverse=True\n            ):\n                try:\n                    uts_create = date_to_uts_utc(certificate[\"created_at\"])\n                except Exception as _err:\n                    self.logger.error(\n                        \"Date conversion error during certificate reusage check: id:%s/created_at:%s\",\n                        certificate[\"id\"],\n                        certificate[\"created_at\"],\n                    )\n                    uts_create = 0\n\n                self.logger.debug(\n                    \"uts: %s, reusage_tf: %s,  uts_create: %s, uts_exp: %s\",\n                    uts,\n                    self.config.cert_reusage_timeframe,\n                    uts_create,\n                    certificate[\"expire_uts\"],\n                )\n                # check if there certificates within reusage timeframe\n                if (\n                    certificate[\"cert_raw\"]\n                    and certificate[\"cert\"]\n                    and uts - self.config.cert_reusage_timeframe <= uts_create\n                    and uts <= certificate[\"expire_uts\"]\n                ):\n                    cert = certificate[\"cert\"]\n                    cert_raw = certificate[\"cert_raw\"]\n                    message = f'reused certificate from id: {certificate[\"id\"]}'\n                    break\n        else:\n            self.logger.debug(\n                \"Certificate._check_certificate_reusability(): no certificates found\"\n            )\n\n        self.logger.debug(\n            \"Certificate._check_certificate_reusability() ended with %s\", message\n        )\n        return (None, cert, cert_raw, message)\n\n    def _load_hooks_configuration(self, config_dic: Dict[str, str]):\n        \"\"\"Load hook configuration from config dictionary\"\"\"\n        self.logger.debug(\"Certificate._load_hooks_configuration()\")\n\n        # load hooks according to configuration\n        hooks_module = hooks_load(self.logger, config_dic)\n        if hooks_module:\n            try:\n                # store handler in variable\n                self.hooks = hooks_module.Hooks(self.logger)\n            except Exception as err:\n                self.logger.critical(\"Enrollment hooks could not be loaded: %s\", err)\n\n        # Hooks section\n        if \"Hooks\" in config_dic:\n            self.config.ignore_pre_hook_failure = config_dic.getboolean(\n                \"Hooks\", \"ignore_pre_hook_failure\", fallback=False\n            )\n            self.config.ignore_post_hook_failure = config_dic.getboolean(\n                \"Hooks\", \"ignore_post_hook_failure\", fallback=True\n            )\n            self.config.ignore_success_hook_failure = config_dic.getboolean(\n                \"Hooks\", \"ignore_success_hook_failure\", fallback=False\n            )\n\n        self.logger.debug(\"Certificate._load_hooks_configuration() ended\")\n\n    def _load_certificate_parameters(self, config_dic: Dict[str, str] = None):\n        \"\"\"Load various certificate parameters - now handled by CertificateConfig\"\"\"\n        self.logger.debug(\n            \"Certificate._load_certificate_parameters() - delegated to CertificateConfig\"\n        )\n        # Certificate section\n        try:\n            self.config.cert_reusage_timeframe = int(\n                config_dic.get(\n                    \"Certificate\",\n                    \"cert_reusage_timeframe\",\n                    fallback=0,\n                )\n            )\n        except Exception:\n            self.logger.error(\n                \"Invalid cert_reusage_timeframe value in configuration, using default of 0 seconds\"\n            )\n\n        try:\n            self.config.enrollment_timeout = int(\n                config_dic.get(\"Certificate\", \"enrollment_timeout\", fallback=5)\n            )\n        except Exception:\n            self.logger.error(\n                \"Invalid enrollment_timeout value in configuration, using default of 5 seconds\"\n            )\n\n        try:\n            self.config.retry_after = int(\n                config_dic.get(\"Certificate\", \"retry_after\", fallback=600)\n            )\n        except Exception:\n            self.logger.error(\n                \"Invalid retry_after value in configuration, using default of 600 seconds\"\n            )\n\n        self.config.cert_operations_log = config_dic.get(\n            \"Certificate\", \"cert_operations_log\", fallback=None\n        )\n        if self.config.cert_operations_log:\n            self.config.cert_operations_log = self.config.cert_operations_log.lower()\n\n        # Order section\n        if \"Order\" in config_dic:\n            self.config.tnauthlist_support = config_dic.getboolean(\n                \"Order\", \"tnauthlist_support\", fallback=False\n            )\n\n        # CAhandler section\n        if \"CAhandler\" in config_dic:\n            handler_file = config_dic.get(\"CAhandler\", \"handler_file\", fallback=None)\n            if handler_file is not None and handler_file.endswith(\"asa_ca_handler.py\"):\n                self.logger.debug(\n                    \"Certificate._load_certificate_parameters(): enabling cn2san_add for asa_ca_handler\"\n                )\n                self.config.cn2san_add = True\n\n        # Directory section\n        if \"Directory\" in config_dic and \"url_prefix\" in config_dic[\"Directory\"]:\n            self.path_dic = {\n                k: config_dic[\"Directory\"][\"url_prefix\"] + v\n                for k, v in self.path_dic.items()\n            }\n\n        self.config.async_mode = config_async_mode_load(\n            self.logger, config_dic, self.dbstore.type\n        )\n\n        self.logger.debug(\"Certificate._load_certificate_parameters() ended\")\n\n    def _load_configuration(self):\n        \"\"\"Load certificate configuration from file\"\"\"\n        self.logger.debug(\"Certificate._load_configuration()\")\n        config_dic = load_config()\n\n        # load ca_handler according to configuration\n        ca_handler_module = ca_handler_load(self.logger, config_dic)\n\n        if ca_handler_module:\n            # store handler in variable\n            self.cahandler = ca_handler_module.CAhandler\n        else:\n            self.logger.critical(\"No ca_handler loaded\")\n\n        # load hooks\n        self._load_hooks_configuration(config_dic)\n\n        # load certificate parameters\n        self._load_certificate_parameters(config_dic)\n\n        # Update CertificateLogger with the loaded configuration\n        self.certificate_logger.cert_operations_log = self.config.cert_operations_log\n\n        self.logger.debug(\"ca_handler: %s\", ca_handler_module)\n        self.logger.debug(\"Certificate._load_configuration() ended.\")\n\n    def _load_and_validate_identifiers(\n        self, identifier_dic: Dict[str, str], csr: str\n    ) -> List[str]:\n        self.logger.debug(\"Certificate._load_and_validate_identifiers()\")\n        # load identifiers\n        try:\n            identifiers = json.loads(identifier_dic[\"identifiers\"].lower())\n        except Exception:\n            identifiers = []\n\n        # do we need to check for tnauth\n        tnauthlist_identifer_in = self._check_for_tnauth_identifiers(identifiers)\n\n        if self.config.tnauthlist_support and tnauthlist_identifer_in:\n            # get list of certextensions in base64 format\n            try:\n                tnauthlist = csr_extensions_get(self.logger, csr)\n                identifier_status = self._validate_identifiers_against_tnauthlist(\n                    identifier_dic, tnauthlist\n                )\n            except Exception as err_:\n                identifier_status = []\n                self.logger.warning(\n                    \"Error while parsing CSR for TNAuthList identifier check: %s\", err_\n                )\n        else:\n            # get sans and compare identifiers against san\n            try:\n                san_list = csr_san_get(self.logger, csr)\n                identifier_status = self._validate_identifiers_against_sans(\n                    identifiers, san_list\n                )\n            except Exception as err_:\n                identifier_status = []\n                self.logger.warning(\n                    \"Error while checking identifiers against SAN: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\n            \"Certificate._load_and_validate_identifiers() ended with %s\",\n            identifier_status,\n        )\n        return identifier_status\n\n    def _validate_csr_against_order(self, certificate_name: str, csr: str) -> bool:\n        \"\"\"Validate CSR extensions against order requirements\"\"\"\n        self.logger.debug(\"Certificate._validate_csr_against_order()\")\n\n        # fetch certificate dictionary from DB\n        certificate_dic = self._get_certificate_info(certificate_name)\n        self.logger.debug(\n            \"Certificate._get_certificate_info() ended with:%s\", certificate_dic\n        )\n\n        # empty list of statuses\n        identifier_status = []\n\n        if \"order\" in certificate_dic:\n            # get identifiers for order\n            try:\n                identifier_dic = self.repository.order_lookup(\n                    \"name\", certificate_dic[\"order\"], [\"identifiers\"]\n                )\n            except Exception as err_:\n                self.logger.critical(\n                    \"Database error in Certificate when checking the CSR identifiers: %s\",\n                    err_,\n                )\n                identifier_dic = {}\n\n            if identifier_dic and \"identifiers\" in identifier_dic:\n                identifier_status = self._load_and_validate_identifiers(\n                    identifier_dic, csr\n                )\n\n        csr_check_result = False\n\n        if identifier_status and False not in identifier_status:\n            csr_check_result = True\n\n        self.logger.debug(\n            \"Certificate._validate_csr_against_order() ended with %s\", csr_check_result\n        )\n        return csr_check_result\n\n    def _process_certificate_enrollment(self, csr: str) -> Tuple[str, str, str, str]:\n        self.logger.debug(\"Certificate._process_certificate_enrollment()\")\n\n        poll_identifier = None\n        error = None\n        if self.config.cert_reusage_timeframe:\n            (\n                error,\n                certificate,\n                certificate_raw,\n                poll_identifier,\n            ) = self._check_certificate_reusability(csr)\n        else:\n            certificate = None\n            certificate_raw = None\n\n        if not certificate or not certificate_raw:\n            self.logger.debug(\n                \"Certificate._process_certificate_enrollment(): trigger enrollment\"\n            )\n            with self.cahandler(self.debug, self.logger) as ca_handler:\n                (\n                    error,\n                    certificate,\n                    certificate_raw,\n                    poll_identifier,\n                ) = ca_handler.enroll(csr)\n            cert_reusage = False\n        else:\n            self.logger.info(\"Reuse existing certificate\")\n            cert_reusage = True\n\n        self.logger.debug(\"Certificate._process_certificate_enrollment() ended\")\n        return (error, certificate, certificate_raw, poll_identifier, cert_reusage)\n\n    def _get_certificate_renewal_info(self, certificate: str) -> str:\n        \"\"\"get renewal info\"\"\"\n        self.logger.debug(\"Certificate._renewal_info_get()\")\n\n        certificate_list = pembundle_to_list(self.logger, certificate)\n\n        renewal_info_hex = certid_asn1_get(\n            self.logger, certificate_list[0], certificate_list[1]\n        )\n\n        self.logger.debug(\n            \"Certificate.certid_asn1_get() ended with %s\", renewal_info_hex\n        )\n        return renewal_info_hex\n\n    def _store_certificate_and_update_order(\n        self,\n        certificate: str,\n        certificate_raw: str,\n        poll_identifier: str,\n        certificate_name: str,\n        order_name: str,\n        csr: str,\n    ) -> Tuple[int, str]:\n        \"\"\"Store certificate and update order status\"\"\"\n        self.logger.debug(\"Certificate._store_certificate_and_update_order()\")\n\n        error = None\n        (issue_uts, expire_uts) = cert_dates_get(self.logger, certificate_raw)\n        try:\n            result = self._store_certificate_in_database(\n                certificate_name,\n                certificate,\n                certificate_raw,\n                issue_uts,\n                expire_uts,\n                poll_identifier,\n            )\n            if result:\n                self._update_order_status({\"name\": order_name, \"status\": \"valid\"})\n            if self.hooks:\n                try:\n                    self.hooks.success_hook(\n                        certificate_name,\n                        order_name,\n                        csr,\n                        certificate,\n                        certificate_raw,\n                        poll_identifier,\n                    )\n                    self.logger.debug(\n                        \"Certificate._store_certificate_and_update_order: success_hook successful\"\n                    )\n                except Exception as err:\n                    self.logger.error(\n                        \"Exception during success_hook execution: %s\", err\n                    )\n                    if not self.config.ignore_success_hook_failure:\n                        error = (None, \"success_hook_error\", str(err))\n\n        except Exception as err_:\n            result = None\n            self.logger.critical(\n                \"Database error: failed to store certificate: %s\", err_\n            )\n            error = self.err_msg_dic.get(\n                \"serverinternal\", \"Unknown error\"\n            )  # Ensure error is set\n\n        self.logger.debug(\"Certificate._store_certificate_and_update_order() ended\")\n        return (result, error)\n\n    def _handle_enrollment_error(\n        self, error: str, poll_identifier: str, order_name: str, certificate_name: str\n    ) -> Tuple[None, str, str]:\n        \"\"\"Store error message for later analysis\"\"\"\n        self.logger.debug(\"Certificate._handle_enrollment_error(%s)\", error)\n\n        result = None\n        detail = None\n        try:\n            if not poll_identifier:\n                self.logger.debug(\n                    \"Certificate._handle_enrollment_error(): invalidating order as there is no certificate and no poll_identifier: %s/%s\",\n                    error,\n                    order_name,\n                )\n                self._update_order_status({\"name\": order_name, \"status\": \"invalid\"})\n            self._store_certificate_error(certificate_name, error, poll_identifier)\n        except Exception as err_:\n            result = None\n            self.logger.critical(\n                \"Database error: failed to store certificate error: %s\", err_\n            )\n\n        # cover polling cases\n        if poll_identifier:\n            detail = poll_identifier\n        elif error == \"Either CN or SANs are not allowed by configuration\":\n            error = self.err_msg_dic[\"rejectedidentifier\"]\n            detail = \"CN or SANs are not allowed by configuration\"\n        else:\n            error = self.err_msg_dic[\"serverinternal\"]\n        self.logger.debug(\n            \"Certificate._handle_enrollment_error() ended with: %s\", result\n        )\n        return (result, error, detail)\n\n    def _execute_pre_enrollment_hooks(\n        self, certificate_name: str, order_name: str, csr: str\n    ) -> List[str]:\n        self.logger.debug(\n            \"Certificate._execute_pre_enrollment_hooks(%s, %s)\",\n            certificate_name,\n            order_name,\n        )\n        hook_error = []\n        if self.hooks:\n            try:\n                self.hooks.pre_hook(certificate_name, order_name, csr)\n                self.logger.debug(\n                    \"Certificate._execute_pre_enrollment_hooks(): pre_hook successful\"\n                )\n            except Exception as err:\n                self.logger.error(\"Exception during pre_hook execution: %s\", err)\n                if not self.config.ignore_pre_hook_failure:\n                    hook_error = (None, \"pre_hook_error\", str(err))\n\n        self.logger.debug(\"Certificate._execute_pre_enrollment_hooks(%s)\", hook_error)\n        return hook_error\n\n    def _execute_post_enrollment_hooks(\n        self, certificate_name: str, order_name: str, csr: str, error: str\n    ) -> List[str]:\n        self.logger.debug(\n            \"Certificate._execute_post_enrollment_hooks(%s, %s\",\n            certificate_name,\n            order_name,\n        )\n        hook_error = []\n        if self.hooks:\n            try:\n                self.hooks.post_hook(certificate_name, order_name, csr, error)\n                self.logger.debug(\n                    \"Certificate._execute_post_enrollment_hooks(): post_hook successful\"\n                )\n            except Exception as err:\n                self.logger.error(\"Exception during post_hook execution: %s\", err)\n                if not self.config.ignore_post_hook_failure:\n                    hook_error.append(\n                        str(err)\n                    )  # Append error message to hook_error list\n\n        self.logger.debug(\"Certificate._execute_post_enrollment_hooks(%s)\", hook_error)\n        return hook_error\n\n    def _process_enrollment_and_store_certificate(\n        self, certificate_name: str, csr: str, order_name: str = None\n    ) -> Tuple[str, str, str]:\n        \"\"\"Process certificate enrollment and store the result\"\"\"\n        self.logger.debug(\n            \"Certificate._process_enrollment_and_store_certificate(%s, %s, %s)\",\n            certificate_name,\n            order_name,\n            csr,\n        )\n\n        detail = None\n        error = None\n\n        hook_error = self._execute_pre_enrollment_hooks(\n            certificate_name, order_name, csr\n        )\n        if hook_error:\n            return hook_error\n\n        # enroll certificate\n        (\n            error,\n            certificate,\n            certificate_raw,\n            poll_identifier,\n            cert_reusage,\n        ) = self._process_certificate_enrollment(csr)\n        if certificate:\n            (result, error) = self._store_certificate_and_update_order(\n                certificate,\n                certificate_raw,\n                poll_identifier,\n                certificate_name,\n                order_name,\n                csr,\n            )\n            if error:\n                return error\n            elif self.config.cert_operations_log:\n                try:\n                    self.certificate_logger.log_certificate_issuance(\n                        certificate_name, certificate_raw, order_name, cert_reusage\n                    )\n                except Exception as log_exc:\n                    self.logger.error(\n                        \"Exception during log_certificate_issuance: %s\", log_exc\n                    )\n\n        else:\n            self.logger.error(\"Enrollment error: %s\", error)\n            (result, error, detail) = self._handle_enrollment_error(\n                error, poll_identifier, order_name, certificate_name\n            )\n\n        hook_error = self._execute_post_enrollment_hooks(\n            certificate_name, order_name, csr, error\n        )\n        if hook_error:\n            return hook_error\n\n        self.logger.debug(\n            \"Certificate._process_enrollment_and_store_certificate() ended with: %s:%s\",\n            result,\n            error,\n        )\n        return (result, error, detail)\n\n    def _check_identifier_match(\n        self, cert_type: str, cert_value: str, identifiers: List[str], san_is_in: bool\n    ) -> bool:\n        \"\"\"Check if identifier matches certificate values\"\"\"\n        self.logger.debug(\n            \"Certificate._check_identifier_match(%s/%s)\", cert_type, cert_value\n        )\n\n        if cert_type and cert_value:\n            for identifier in identifiers:\n                if (\n                    \"type\" in identifier\n                    and identifier[\"type\"].lower() == cert_type\n                    and identifier[\"value\"].lower() == cert_value\n                ):\n                    san_is_in = True\n                    break\n\n        self.logger.debug(\"Certificate._check_identifier_match(%s)\", san_is_in)\n        return san_is_in\n\n    def _validate_identifiers_against_sans(\n        self, identifiers: List[str], san_list: List[str]\n    ) -> List[str]:\n        \"\"\"Compare identifiers and check if each SAN is in identifier list\"\"\"\n        self.logger.debug(\"Certificate._validate_identifiers_against_sans()\")\n\n        identifier_status = []\n        for san in san_list:\n            san_is_in = False\n            try:\n                (cert_type, cert_value) = san.lower().split(\":\", 1)\n            except Exception as err_:\n                self.logger.error(\"Error while splitting san %s: %s\", san, err_)\n                cert_type = None\n                cert_value = None\n\n            san_is_in = self._check_identifier_match(\n                cert_type, cert_value, identifiers, san_is_in\n            )\n\n            self.logger.debug(\n                \"SAN check for %s against identifiers returned %s\",\n                san.lower(),\n                san_is_in,\n            )\n            identifier_status.append(san_is_in)\n\n        if not identifier_status:\n            self.logger.error(\"No SANs found in certificate\")\n            identifier_status.append(False)\n\n        self.logger.debug(\n            \"Certificate._validate_identifiers_against_sans() ended with %s\",\n            identifier_status,\n        )\n        return identifier_status\n\n    def _check_tnauth_identifier_match(\n        self, identifier: Dict[str, str], tnauthlist: List[str]\n    ) -> bool:\n        \"\"\"Check TNAuth identifier against TNAuth list\"\"\"\n        self.logger.debug(\"Certificate._check_tnauth_identifier_match(%s)\", identifier)\n\n        result = False\n        # get the tnauthlist identifier\n        if \"type\" in identifier and identifier[\"type\"].lower() == \"tnauthlist\":\n            # check if tnauthlist extension is in extension list\n            if \"value\" in identifier and identifier[\"value\"] in tnauthlist:\n                result = True\n\n        self.logger.debug(\n            \"Certificate._check_tnauth_identifier_match() ended with %s\", result\n        )\n        return result\n\n    def _validate_identifiers_against_tnauthlist(\n        self, identifier_dic: Dict[str, str], tnauthlist: List[str]\n    ):\n        \"\"\"Compare identifiers and check if each is in TNAuth list\"\"\"\n        self.logger.debug(\"Certificate._validate_identifiers_against_tnauthlist()\")\n\n        identifier_status = []\n        # reload identifiers (case senetive)\n        try:\n            identifiers = json.loads(identifier_dic[\"identifiers\"])\n        except Exception:\n            identifiers = []\n\n        if tnauthlist and not identifier_dic:\n            identifier_status.append(False)\n        elif identifiers and tnauthlist:\n            for identifier in identifiers:\n                identifier_status.append(\n                    self._check_tnauth_identifier_match(identifier, tnauthlist)\n                )\n        else:\n            identifier_status.append(False)\n\n        self.logger.debug(\n            \"Certificate._validate_identifiers_against_tnauthlist() ended with %s\",\n            identifier_status,\n        )\n        return identifier_status\n\n    def _get_certificate_info(\n        self,\n        certificate_name: str,\n        flist: List[str] = (\"name\", \"csr\", \"cert\", \"order__name\"),\n    ) -> Dict[str, str]:\n        \"\"\"Get certificate information from database\"\"\"\n        self.logger.debug(\"Certificate._get_certificate_info(%s)\", certificate_name)\n        try:\n            result = self.repository.certificate_lookup(\"name\", certificate_name, flist)\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to get certificate info: %s\", err_\n            )\n            result = None\n        return result\n\n    def _update_order_status(self, data_dic: Dict[str, str]):\n        \"\"\"Update order status based on order name\"\"\"\n        self.logger.debug(\"Certificate._update_order_status(%s)\", data_dic)\n        try:\n            self.repository.order_update(data_dic)\n        except Exception as err_:\n            self.logger.critical(\"Database error: failed to update order: %s\", err_)\n\n    def _validate_revocation_reason(self, reason: str) -> str:\n        \"\"\"Validate revocation reason code\"\"\"\n        self.logger.debug(\"Certificate._validate_revocation_reason(%s)\", reason)\n\n        # taken from https://tools.ietf.org/html/rfc5280#section-5.3.1\n        allowed_reasons = {\n            0: \"unspecified\",\n            1: \"keyCompromise\",\n            # 2: 'cACompromise',\n            3: \"affiliationChanged\",\n            4: \"superseded\",\n            5: \"cessationOfOperation\",\n            6: \"certificateHold\",\n            # 8: 'removeFromCRL',\n            # 9: 'privilegeWithdrawn',\n            # 10: 'aACompromise'\n        }\n\n        result = allowed_reasons.get(reason, None)\n        self.logger.debug(\n            \"Certificate._validate_revocation_reason() ended with %s\", result\n        )\n        return result\n\n    def _validate_revocation_request(\n        self, account_name: str, payload: Dict[str, str]\n    ) -> Tuple[int, str]:\n        \"\"\"Validate revocation request for consistency\"\"\"\n        self.logger.debug(\"Certificate._validate_revocation_request(%s)\", account_name)\n\n        # set a value to avoid that we are returning none by accident\n        code = 400\n        error = None\n        if \"reason\" in payload:\n            # check revocatoin reason if we get one\n            rev_reason = self._validate_revocation_reason(payload[\"reason\"])\n            # successful\n            if not rev_reason:\n                error = self.err_msg_dic[\"badrevocationreason\"]\n        else:\n            # set revocation reason to unspecified\n            rev_reason = \"unspecified\"\n\n        if rev_reason:\n            # check if the account issued the certificate and return the order name\n            if \"certificate\" in payload:\n                order_name = self._validate_certificate_account_ownership(\n                    account_name, payload[\"certificate\"]\n                )\n            else:\n                self.logger.debug(\n                    \"Certificate._validate_revocation_request(): Revocation request missing 'certificate' field\"\n                )\n                order_name = None\n\n            error = rev_reason\n            if order_name:\n                # check if the account holds the authorization for the identifiers\n                auth_chk = self._validate_order_authorization(\n                    order_name, payload[\"certificate\"]\n                )\n                if auth_chk:\n                    # all good set code to 200\n                    code = 200\n                else:\n                    error = self.err_msg_dic[\"unauthorized\"]\n\n        self.logger.debug(\n            \"Certificate._validate_revocation_request() ended with: %s, %s\", code, error\n        )\n        return (code, error)\n\n    def _store_certificate_in_database(\n        self,\n        certificate_name: str,\n        certificate: str,\n        raw: str,\n        issue_uts: int = 0,\n        expire_uts: int = 0,\n        poll_identifier: str = None,\n    ) -> int:\n        \"\"\"Store certificate in database\"\"\"\n        self.logger.debug(\n            \"Certificate._store_certificate_in_database(%s)\", certificate_name\n        )\n\n        renewal_info_hex = self._get_certificate_renewal_info(certificate)\n        serial = cert_serial_get(self.logger, raw, hexformat=True)\n        aki = cert_aki_get(self.logger, raw)\n\n        data_dic = {\n            \"cert\": certificate,\n            \"name\": certificate_name,\n            \"cert_raw\": raw,\n            \"issue_uts\": issue_uts,\n            \"expire_uts\": expire_uts,\n            \"poll_identifier\": poll_identifier,\n            \"renewal_info\": renewal_info_hex,\n            \"serial\": serial,\n            \"aki\": aki,\n        }\n        try:\n            cert_id = self.repository.certificate_add(data_dic)\n        except Exception as err_:\n            cert_id = None\n            self.logger.critical(\n                \"acme2certifier database error in Certificate._store_certificate_in_database(): %s\",\n                err_,\n            )\n        self.logger.debug(\n            \"Certificate._store_certificate_in_database(%s) ended\", cert_id\n        )\n        return cert_id\n\n    def _store_certificate_error(\n        self, certificate_name: str, error: str, poll_identifier: str\n    ) -> int:\n        \"\"\"Store certificate error information in database\"\"\"\n        self.logger.debug(\"Certificate._store_certificate_error(%s)\", certificate_name)\n        data_dic = {\n            \"error\": error,\n            \"name\": certificate_name,\n            \"poll_identifier\": poll_identifier,\n        }\n        try:\n            cert_id = self.repository.certificate_add(data_dic)\n        except Exception as err_:\n            cert_id = None\n            self.logger.critical(\n                \"Database error: failed to store certificate error: %s\", err_\n            )\n        self.logger.debug(\"Certificate._store_certificate_error(%s) ended\", cert_id)\n        return cert_id\n\n    def _check_for_tnauth_identifiers(self, identifier_dic: Dict[str, str]) -> int:\n        \"\"\"Check if we have TNAuth list identifiers\"\"\"\n        self.logger.debug(\"Certificate._check_for_tnauth_identifiers()\")\n        # check if we have a tnauthlist identifier\n        tnauthlist_identifer_in = False\n        if identifier_dic:\n            for identifier in identifier_dic:\n                if \"type\" in identifier:\n                    if identifier[\"type\"].lower() == \"tnauthlist\":\n                        tnauthlist_identifer_in = True\n        self.logger.debug(\n            \"Certificate._check_for_tnauth_identifiers() ended with: %s\",\n            tnauthlist_identifer_in,\n        )\n        return tnauthlist_identifer_in\n\n    def certlist_search(\n        self,\n        key: str,\n        value: Union[str, int],\n        vlist: List[str] = None,\n    ) -> Dict[str, str]:\n        \"\"\"get certificate from database\"\"\"\n        self.logger.debug(\"Certificate.certlist_search(%s: %s)\", key, value)\n\n        if vlist is None:\n            vlist = [\"name\", \"csr\", \"cert\", \"order__name\"]\n\n        # Delegate to certificate manager for search with business logic\n        search_result = self.certificate_manager.search_certificates(key, value, vlist)\n\n        # Return certificates list for backward compatibility\n        return search_result.get(\"certificates\", None)\n\n    def cleanup(\n        self, timestamp: int = None, purge: bool = False\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"cleanup routine to shrink table-size\"\"\"\n        self.logger.debug(\"Certificate.cleanup(%s,%s)\", timestamp, purge)\n\n        if not timestamp:\n            timestamp = uts_now()\n\n        # Delegate to certificate manager\n        (field_list, report_list) = self.certificate_manager.cleanup_certificates(\n            timestamp, purge\n        )\n\n        self.logger.debug(\n            \"Certificate.cleanup() ended with: %s certs\", len(report_list)\n        )\n        return (field_list, report_list)\n\n    def _update_certificate_dates(self, cert: Dict[str, str]):\n        \"\"\"Update issue and expiry date with date from certificate\"\"\"\n        self.logger.debug(\"Certificate._update_certificate_dates()\")\n\n        if \"issue_uts\" in cert and \"expire_uts\" in cert:\n            if cert[\"issue_uts\"] == 0 and cert[\"expire_uts\"] == 0:\n                if cert[\"cert_raw\"]:\n                    (issue_uts, expire_uts) = cert_dates_get(\n                        self.logger, cert[\"cert_raw\"]\n                    )\n                    if issue_uts or expire_uts:\n                        self._store_certificate_in_database(\n                            cert[\"name\"],\n                            cert[\"cert\"],\n                            cert[\"cert_raw\"],\n                            issue_uts,\n                            expire_uts,\n                        )\n            else:\n                self.logger.debug(\n                    \"Certificate._update_certificate_dates(): certificate %s already has issue and expiry dates - skipping update\",\n                    cert[\"name\"],\n                )\n\n        self.logger.debug(\"Certificate._update_certificate_dates() ended\")\n\n    def dates_update(self):\n        \"\"\"scan certificates and update issue/expiry date\"\"\"\n        self.logger.debug(\"Certificate.dates_update()\")\n\n        # For backward compatibility with tests, get certificate list and process each\n        cert_list = self.certlist_search(\n            \"issue_uts\",\n            0,\n            vlist=[\"id\", \"name\", \"cert\", \"cert_raw\", \"issue_uts\", \"expire_uts\"],\n        )\n        if cert_list:\n            for cert in cert_list:\n                self._update_certificate_dates(cert)\n\n        self.logger.debug(\"Certificate.dates_update() ended\")\n\n    def _handle_enrollment_thread_execution(\n        self, certificate_name: str, csr: str, order_name: str\n    ) -> Tuple[str, str]:\n        \"\"\"Handle the threaded enrollment execution with proper error handling\"\"\"\n        try:\n            twrv = ThreadWithReturnValue(\n                target=self._process_enrollment_and_store_certificate,\n                args=(certificate_name, csr, order_name),\n            )\n            twrv.daemon = True\n            twrv.start()\n            if self.config.async_mode:\n                enroll_result = (None, None, \"asynchronous enrollment started\")\n            else:\n                enroll_result = twrv.join(timeout=self.config.enrollment_timeout)\n\n            self.logger.debug(\"Certificate enrollment thread completed\")\n\n            if enroll_result is None:\n                return \"timeout\", \"Enrollment process timed out\"\n\n            return self._parse_enrollment_result(enroll_result)\n\n        except Exception as err:\n            self.logger.error(\"Error during threaded enrollment execution: %s\", err)\n            return (\n                self.err_msg_dic[\"serverinternal\"],\n                \"Enrollment thread execution failed\",\n            )\n\n    def _parse_enrollment_result(self, enroll_result) -> Tuple[str, str]:\n        \"\"\"Parse enrollment result with proper error handling\"\"\"\n        self.logger.debug(\"Certificate._parse_enrollment_result(%s)\", enroll_result)\n        if isinstance(enroll_result, tuple) and len(enroll_result) >= 2:\n            _, error, *detail = enroll_result\n            return error, detail[0] if detail else \"\"\n        else:\n            self.logger.error(\"Unexpected enrollment result format: %s\", enroll_result)\n            return (\n                self.err_msg_dic[\"serverinternal\"],\n                \"Unexpected enrollment result format\",\n            )\n\n    def process_certificate_enrollment_request(\n        self, certificate_name: str, csr: str, order_name: str = None\n    ) -> Tuple[str, str]:\n        \"\"\"Process certificate enrollment request and validate CSR with improved error handling\"\"\"\n        try:\n            # Validate input parameters\n            validation_errors = self._validate_input_parameters(\n                certificate_name=certificate_name, csr=csr\n            )\n            if validation_errors:\n                self.logger.error(self.INVALID_INPUT_PARAMS_MSG, validation_errors)\n                return self.err_msg_dic[\"badcsr\"], \"Invalid input parameters\"\n\n            self.logger.debug(\n                \"Certificate.process_certificate_enrollment_request(%s, %s)\",\n                certificate_name,\n                order_name,\n            )\n\n            # Validate CSR against order\n            try:\n                csr_check_result = self._validate_csr_against_order(\n                    certificate_name, csr\n                )\n            except Exception as err:\n                self.logger.error(\"Error validating CSR against order: %s\", err)\n                return self.err_msg_dic[\"serverinternal\"], \"CSR validation failed\"\n\n            if not csr_check_result:\n                return self.err_msg_dic[\"badcsr\"], \"CSR validation failed\"\n\n            # Process enrollment\n            error, detail = self._handle_enrollment_thread_execution(\n                certificate_name, csr, order_name\n            )\n            self.logger.debug(\n                \"Certificate.process_certificate_enrollment_request() ended with: %s:%s\",\n                error,\n                detail,\n            )\n            return (error, detail)\n\n        except Exception as err:\n            self.logger.critical(\n                \"Unexpected error in process_certificate_enrollment_request: %s\", err\n            )\n            return (\n                self.err_msg_dic[\"serverinternal\"],\n                \"Unexpected error during enrollment\",\n            )\n\n    def _determine_certificate_response(self, cert_info: Dict) -> Dict[str, str]:\n        \"\"\"Determine appropriate response based on certificate info\"\"\"\n        self.logger.debug(\"Certificate._determine_certificate_response()\")\n\n        if not cert_info or \"order__status_id\" not in cert_info:\n            return self._create_error_response(500, self.err_msg_dic[\"serverinternal\"])\n\n        order_status = cert_info[\"order__status_id\"]\n\n        if order_status == self.ORDER_STATUS_VALID:\n            return self._handle_valid_certificate(cert_info)\n        elif order_status == self.ORDER_STATUS_PROCESSING:\n            return self._handle_processing_certificate()\n        else:\n            return self._create_error_response(403, self.err_msg_dic[\"ordernotready\"])\n\n    def _handle_valid_certificate(self, cert_info: Dict) -> Dict[str, str]:\n        \"\"\"Handle response for valid certificate\"\"\"\n        if \"cert\" in cert_info and cert_info[\"cert\"]:\n            return {\n                \"code\": 200,\n                \"data\": cert_info[\"cert\"],\n                \"header\": {\"Content-Type\": \"application/pem-certificate-chain\"},\n            }\n        else:\n            return self._create_error_response(500, self.err_msg_dic[\"serverinternal\"])\n\n    def _handle_processing_certificate(self) -> Dict[str, str]:\n        \"\"\"Handle response for processing certificate\"\"\"\n        return {\n            \"code\": 403,\n            \"data\": self.err_msg_dic[\"ratelimited\"],\n            \"header\": {\"Retry-After\": f\"{self.config.retry_after}\"},\n        }\n\n    def get_certificate_details(self, url: str) -> Dict[str, str]:\n        \"\"\"Get certificate details from URL with improved error handling\"\"\"\n        try:\n            # Validate input\n            validation_errors = self._validate_input_parameters(url=url)\n            if validation_errors:\n                self.logger.error(self.INVALID_INPUT_PARAMS_MSG, validation_errors)\n                return self._create_error_response(400, \"Invalid URL parameter\")\n\n            certificate_name = string_sanitize(\n                self.logger,\n                url.replace(f'{self.server_name}{self.path_dic[\"cert_path\"]}', \"\"),\n            )\n            self.logger.debug(\n                \"Certificate.get_certificate_details(%s)\", certificate_name\n            )\n\n            # Get certificate info using manager with error handling\n            try:\n                cert_info = self.certificate_manager.get_certificate_info(\n                    certificate_name\n                )\n            except Exception as err:\n                self.logger.error(\"Error retrieving certificate info: %s\", err)\n                return self._create_error_response(\n                    500, self.err_msg_dic[\"serverinternal\"]\n                )\n\n            response_dic = self._determine_certificate_response(cert_info)\n            self.logger.debug(\n                \"Certificate.get_certificate_details(%s) ended\", response_dic[\"code\"]\n            )\n            return response_dic\n\n        except Exception as err:\n            self.logger.critical(\"Unexpected error in get_certificate_details: %s\", err)\n            return self._create_error_response(500, self.err_msg_dic[\"serverinternal\"])\n\n    def _validate_certificate_request_message(\n        self, content: str\n    ) -> Tuple[int, str, str, Dict, Dict, str]:\n        \"\"\"Validate certificate request message\"\"\"\n        try:\n            return self.message.check(content)\n        except Exception as err:\n            self.logger.error(\"Error validating certificate request message: %s\", err)\n            return (\n                400,\n                self.err_msg_dic[\"malformed\"],\n                \"Message validation failed\",\n                {},\n                {},\n                \"\",\n            )\n\n    def _prepare_certificate_response(\n        self, response_dic: Dict, code: int, message: str, detail: str\n    ) -> Dict[str, str]:\n        \"\"\"Prepare and format certificate response\"\"\"\n        try:\n            status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n            response_dic = self.message.prepare_response(response_dic, status_dic)\n\n            # Serialize dict data to JSON if needed\n            if isinstance(response_dic.get(\"data\"), dict):\n                response_dic[\"data\"] = json.dumps(response_dic[\"data\"])\n\n            return response_dic\n        except Exception as err:\n            self.logger.error(\"Error preparing certificate response: %s\", err)\n            return {\n                \"code\": 500,\n                \"data\": self.err_msg_dic[\"serverinternal\"],\n                \"detail\": \"Response formatting failed\",\n            }\n\n    def process_certificate_request(self, content: str) -> Dict[str, str]:\n        \"\"\"Process certificate request with improved error handling and reduced complexity\"\"\"\n        try:\n            # Validate input\n            validation_errors = self._validate_input_parameters(content=content)\n            if validation_errors:\n                self.logger.error(self.INVALID_INPUT_PARAMS_MSG, validation_errors)\n                return self._prepare_certificate_response(\n                    {}, 400, self.err_msg_dic[\"malformed\"], \"Invalid content parameter\"\n                )\n\n            self.logger.debug(\"Certificate.process_certificate_request()\")\n\n            # Validate and parse message\n            (\n                code,\n                message,\n                detail,\n                protected,\n                _payload,\n                _account_name,\n            ) = self._validate_certificate_request_message(content)\n\n            response_dic = {}\n\n            if code == 200:\n                if \"url\" in protected:\n                    try:\n                        response_dic = self.get_certificate_details(protected[\"url\"])\n                        # Update error details if certificate retrieval failed\n                        if response_dic[\"code\"] in (400, 403, 500):\n                            code = response_dic[\"code\"]\n                            message = response_dic[\"data\"]\n                            detail = response_dic.get(\"detail\")\n                    except Exception as err:\n                        self.logger.error(\"Error getting certificate details: %s\", err)\n                        code = 500\n                        message = self.err_msg_dic[\"serverinternal\"]\n                        detail = \"Certificate retrieval failed\"\n                        response_dic = {}\n                else:\n                    code = 400\n                    message = self.err_msg_dic[\"malformed\"]\n                    detail = \"url missing in protected header\"\n                    response_dic = {}\n\n            # Prepare final response\n            final_response = self._prepare_certificate_response(\n                response_dic, code, message, detail\n            )\n\n            result_code = final_response.get(\"code\", \"no code found\")\n            self.logger.debug(\n                \"Certificate.process_certificate_request() ended with: %s\", result_code\n            )\n            return final_response\n\n        except Exception as err:\n            self.logger.critical(\n                \"Unexpected error in process_certificate_request: %s\", err\n            )\n            return self._prepare_certificate_response(\n                {},\n                500,\n                self.err_msg_dic[\"serverinternal\"],\n                \"Unexpected error during request processing\",\n            )\n\n    def _validate_revocation_message(\n        self, content: str\n    ) -> Tuple[int, str, str, str, Dict, str]:\n        \"\"\"Validate revocation message and extract components\"\"\"\n        try:\n            return self.message.check(content)\n        except Exception as err:\n            self.logger.error(\"Error validating revocation message: %s\", err)\n            return (\n                400,\n                self.err_msg_dic[\"malformed\"],\n                \"Message validation failed\",\n                {},\n                {},\n                \"\",\n            )\n\n    def _process_certificate_revocation(\n        self, account_name: str, payload: Dict\n    ) -> Tuple[int, str, str]:\n        \"\"\"Process the actual certificate revocation\"\"\"\n        try:\n            (code, error) = self._validate_revocation_request(account_name, payload)\n            if code != 200:\n                return code, error, None\n\n            # Perform revocation\n            rev_date = uts_to_date_utc(uts_now())\n            with self.cahandler(self.debug, self.logger) as ca_handler:\n                (code, message, detail) = ca_handler.revoke(\n                    payload[\"certificate\"], error, rev_date\n                )\n\n            # Log revocation if configured\n            if self.config.cert_operations_log:\n                try:\n                    self.certificate_logger.log_certificate_revocation(\n                        payload[\"certificate\"], code\n                    )\n                except Exception as log_err:\n                    self.logger.warning(\n                        \"Failed to log certificate revocation: %s\", log_err\n                    )\n\n            return code, message, detail\n\n        except Exception as err:\n            self.logger.error(\"Error during certificate revocation: %s\", err)\n            return (\n                500,\n                self.err_msg_dic[\"serverinternal\"],\n                \"Revocation processing failed\",\n            )\n\n    def revoke_certificate(self, content: str) -> Dict[str, str]:\n        \"\"\"Process certificate revocation request with improved error handling\"\"\"\n        try:\n            # Validate input\n            validation_errors = self._validate_input_parameters(content=content)\n            if validation_errors:\n                self.logger.error(self.INVALID_INPUT_PARAMS_MSG, validation_errors)\n                return self.message.prepare_response(\n                    {},\n                    {\n                        \"code\": 400,\n                        \"type\": self.err_msg_dic[\"malformed\"],\n                        \"detail\": \"Invalid content\",\n                    },\n                )\n\n            self.logger.debug(\"Certificate.revoke_certificate()\")\n\n            # Validate and parse message\n            (\n                code,\n                message,\n                detail,\n                _protected,\n                payload,\n                account_name,\n            ) = self._validate_revocation_message(content)\n\n            if code == 200:\n                if \"certificate\" in payload:\n                    (code, message, detail) = self._process_certificate_revocation(\n                        account_name, payload\n                    )\n                else:\n                    code = 400\n                    message = self.err_msg_dic[\"malformed\"]\n                    detail = \"certificate not found\"\n\n            # Prepare response\n            status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n            response_dic = self.message.prepare_response({}, status_dic)\n\n            self.logger.debug(\n                \"Certificate.revoke_certificate() ended with: %s\", response_dic\n            )\n            return response_dic\n\n        except Exception as err:\n            self.logger.critical(\"Unexpected error in revoke_certificate: %s\", err)\n            error_response = {\n                \"code\": 500,\n                \"type\": self.err_msg_dic[\"serverinternal\"],\n                \"detail\": \"Unexpected error during revocation\",\n            }\n            return self.message.prepare_response({}, error_response)\n\n    def _handle_successful_certificate_poll(\n        self,\n        certificate_name: str,\n        certificate: str,\n        certificate_raw: str,\n        order_name: str,\n    ) -> Optional[int]:\n        \"\"\"Handle successful certificate polling result\"\"\"\n        try:\n            # Get issuing and expiration date\n            (issue_uts, expire_uts) = cert_dates_get(self.logger, certificate_raw)\n\n            # Update certificate record in database\n            result = self._store_certificate_in_database(\n                certificate_name, certificate, certificate_raw, issue_uts, expire_uts\n            )\n\n            # Update order status to valid\n            try:\n                self.repository.order_update({\"name\": order_name, \"status\": \"valid\"})\n            except Exception as err:\n                self.logger.critical(\n                    \"Database error updating order status during polling: %s\", err\n                )\n                # Continue execution as certificate was stored successfully\n\n            return result\n\n        except Exception as err:\n            self.logger.error(\"Error handling successful certificate poll: %s\", err)\n            return None\n\n    def _handle_failed_certificate_poll(\n        self,\n        certificate_name: str,\n        error: str,\n        poll_identifier: str,\n        order_name: str,\n        rejected: bool,\n    ) -> None:\n        \"\"\"Handle failed certificate polling result\"\"\"\n        try:\n            # Store error message for later analysis\n            self._store_certificate_error(certificate_name, error, poll_identifier)\n\n            # Update order status if rejected\n            if rejected:\n                try:\n                    self.repository.order_update(\n                        {\"name\": order_name, \"status\": \"invalid\"}\n                    )\n                except Exception as err:\n                    self.logger.critical(\n                        \"Database error updating order status to invalid: %s\", err\n                    )\n\n        except Exception as err:\n            self.logger.error(\"Error handling failed certificate poll: %s\", err)\n\n    def poll_certificate_status(\n        self, certificate_name: str, poll_identifier: str, csr: str, order_name: str\n    ) -> Optional[int]:\n        \"\"\"Poll certificate status from CA and store result in database with improved error handling\"\"\"\n        try:\n            # Validate input parameters\n            validation_errors = self._validate_input_parameters(\n                certificate_name=certificate_name,\n                poll_identifier=poll_identifier,\n                csr=csr,\n                order_name=order_name,\n            )\n            if validation_errors:\n                self.logger.error(self.INVALID_INPUT_PARAMS_MSG, validation_errors)\n                return None\n\n            self.logger.debug(\n                \"Certificate.poll_certificate_status(%s: %s)\",\n                certificate_name,\n                poll_identifier,\n            )\n\n            # Poll certificate from CA handler\n            try:\n                with self.cahandler(self.debug, self.logger) as ca_handler:\n                    (\n                        error,\n                        certificate,\n                        certificate_raw,\n                        poll_identifier,\n                        rejected,\n                    ) = ca_handler.poll(certificate_name, poll_identifier, csr)\n            except Exception as err:\n                self.logger.error(\"Error polling certificate from CA handler: %s\", err)\n                return None\n\n            # Process poll result\n            if certificate:\n                result = self._handle_successful_certificate_poll(\n                    certificate_name, certificate, certificate_raw, order_name\n                )\n            else:\n                self._handle_failed_certificate_poll(\n                    certificate_name, error, poll_identifier, order_name, rejected\n                )\n                result = None\n\n            self.logger.debug(\n                \"Certificate.poll_certificate_status(%s: %s) ended\",\n                certificate_name,\n                poll_identifier,\n            )\n            return result\n\n        except Exception as err:\n            self.logger.critical(\"Unexpected error in poll_certificate_status: %s\", err)\n            return None\n\n    def store_certificate_signing_request(\n        self, order_name: str, csr: str, header_info: str\n    ) -> str:\n        \"\"\"Store certificate signing request into database with improved error handling\"\"\"\n        self.logger.debug(\n            \"Certificate.store_certificate_signing_request(%s)\", order_name\n        )\n\n        # Delegate to certificate manager for CSR validation and storage\n        try:\n            (\n                success,\n                certificate_name,\n            ) = self.certificate_manager.validate_and_store_csr(\n                order_name, csr, header_info\n            )\n        except Exception as err:\n            self.logger.error(\"Error during CSR validation and storage: %s\", err)\n            certificate_name = \"\"\n            success = False\n\n        if not success:\n            error_msg = f\"Failed to store CSR for order {order_name}\"\n            self.logger.error(error_msg)\n\n        self.logger.debug(\n            \"Certificate.store_certificate_signing_request() ended successfully\"\n        )\n        return certificate_name\n\n    # === Legacy API Compatibility ===\n    # Legacy methods for backward compatibility - use descriptive methods instead\n\n    def enroll_and_store(\n        self, certificate_name: str, csr: str, order_name: str = None\n    ) -> Tuple[str, str]:\n        \"\"\"Legacy API compatibility - use process_certificate_enrollment_request instead.\"\"\"\n        self.logger.debug(\"Certificate.enroll_and_store() called - legacy API\")\n        return self.process_certificate_enrollment_request(\n            certificate_name, csr, order_name\n        )\n\n    def new_get(self, url: str) -> Dict[str, str]:\n        \"\"\"Legacy API compatibility - use get_certificate_details instead.\"\"\"\n        self.logger.debug(\"Certificate.new_get() called - legacy API\")\n        return self.get_certificate_details(url)\n\n    def new_post(self, content: str) -> Dict[str, str]:\n        \"\"\"Legacy API compatibility - use process_certificate_request instead.\"\"\"\n        self.logger.debug(\"Certificate.new_post() called - legacy API\")\n        return self.process_certificate_request(content)\n\n    def revoke(self, content: str) -> Dict[str, str]:\n        \"\"\"Legacy API compatibility - use revoke_certificate instead.\"\"\"\n        self.logger.debug(\"Certificate.revoke() called - legacy API\")\n        return self.revoke_certificate(content)\n\n    def poll(\n        self, certificate_name: str, poll_identifier: str, csr: str, order_name: str\n    ) -> int:\n        \"\"\"Legacy API compatibility - use poll_certificate_status instead.\"\"\"\n        self.logger.debug(\"Certificate.poll() called - legacy API\")\n        return self.poll_certificate_status(\n            certificate_name, poll_identifier, csr, order_name\n        )\n\n    def store_csr(self, order_name: str, csr: str, header_info: str) -> str:\n        \"\"\"Legacy API compatibility - use store_certificate_signing_request instead.\"\"\"\n        self.logger.debug(\"Certificate.store_csr() called - legacy API\")\n        return self.store_certificate_signing_request(order_name, csr, header_info)\n"
  },
  {
    "path": "acme_srv/certificate_business_logic.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Certificate Business Logic - Core Business Rules for Certificate Operations\"\"\"\n\nfrom typing import Dict, Tuple, Union\nfrom acme_srv.helper import (\n    cert_aki_get,\n    cert_cn_get,\n    cert_dates_get,\n    cert_san_get,\n    cert_serial_get,\n    generate_random_string,\n    string_sanitize,\n)\nfrom acme_srv.helpers.csr import csr_load\n\n# Import will be added when needed to avoid circular imports\n# from acme_srv.certificate import CertificateConfig\n\n\nclass CertificateBusinessLogic:\n    \"\"\"\n    Business Logic Layer for Certificate operations.\n\n    This class handles core business rules and processing logic including:\n    - Certificate and CSR validation\n    - Certificate date calculations\n    - Authorization checks\n    - Certificate processing workflows\n    - CA handler interactions\n\n    Follows the Business Logic pattern to encapsulate domain rules.\n    \"\"\"\n\n    def __init__(self, debug: bool = False, logger=None, err_msg_dic=None, config=None):\n        \"\"\"Initialize the Certificate Business Logic\"\"\"\n        self.debug = debug\n        self.logger = logger\n        self.err_msg_dic = err_msg_dic or {}\n\n        # Configuration from dataclass or defaults\n        if config:\n            self.tnauthlist_support = config.tnauthlist_support\n            self.cn2san_add = config.cn2san_add\n            self.cert_reusage_timeframe = config.cert_reusage_timeframe\n        else:\n            self.tnauthlist_support = False\n            self.cn2san_add = False\n            self.cert_reusage_timeframe = 0\n\n    def validate_csr(\n        self, csr: str, _certificate_name: str = None\n    ) -> Tuple[int, str, str]:\n        \"\"\"\n        Validate Certificate Signing Request.\n\n        Args:\n            csr: Certificate Signing Request data\n            certificate_name: Optional certificate name for validation context\n\n        Returns:\n            Tuple of (code, error, detail) indicating validation result\n        \"\"\"\n        self.logger.debug(\"CertificateBusinessLogic.validate_csr()\")\n\n        code = 200\n        error = None\n        detail = None\n\n        try:\n            # Basic CSR validation\n            if not csr:\n                code = 400\n                error = self.err_msg_dic.get(\"badcsr\", \"Invalid CSR\")\n                detail = \"CSR is empty\"\n\n            else:\n                # Additional CSR format validation could go here\n                csr_obj = csr_load(self.logger, csr)\n                if not csr_obj:\n                    code = 400\n                    error = self.err_msg_dic.get(\"badcsr\", \"Invalid CSR\")\n                    detail = \"CSR format is invalid\"\n        except Exception as err:\n            self.logger.error(f\"CSR validation error: {err}\")\n            code = 500\n            error = self.err_msg_dic.get(\"serverinternal\", \"Internal server error\")\n            detail = \"CSR validation failed\"\n\n        self.logger.debug(f\"CertificateBusinessLogic.validate_csr() result: {code}\")\n        return (code, error, detail)\n\n    def calculate_certificate_dates(self, certificate_raw: str) -> Tuple[int, int]:\n        \"\"\"\n        Calculate issue and expiry dates from certificate.\n\n        Args:\n            certificate_raw: Raw certificate data\n\n        Returns:\n            Tuple of (issue_timestamp, expiry_timestamp)\n        \"\"\"\n        self.logger.debug(\"CertificateBusinessLogic.calculate_certificate_dates()\")\n\n        try:\n            (issue_uts, expire_uts) = cert_dates_get(self.logger, certificate_raw)\n        except Exception as err:\n            self.logger.error(f\"Certificate date calculation error: {err}\")\n            issue_uts = 0\n            expire_uts = 0\n\n        return (issue_uts, expire_uts)\n\n    def generate_certificate_name(self) -> str:\n        \"\"\"\n        Generate a random certificate name.\n\n        Returns:\n            Random certificate name string\n        \"\"\"\n        return generate_random_string(self.logger, 12)\n\n    def validate_certificate_data(self, certificate: str) -> bool:\n        \"\"\"\n        Validate certificate data format and structure.\n\n        Args:\n            certificate: Certificate data to validate\n\n        Returns:\n            True if valid, False otherwise\n        \"\"\"\n        self.logger.debug(\"CertificateBusinessLogic.validate_certificate_data()\")\n\n        try:\n            if not certificate or not isinstance(certificate, str):\n                self.logger.warning(\"Empty or non-string certificate data.\")\n                return False\n\n            # Check for PEM format\n            if certificate.strip().startswith(\n                \"-----BEGIN CERTIFICATE-----\"\n            ) and certificate.strip().endswith(\"-----END CERTIFICATE-----\"):\n                return True\n\n            # Optionally, check for DER (base64) format (very basic check)\n            if all(\n                c\n                in \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\\n\\r\"\n                for c in certificate.strip()\n            ):\n                self.logger.info(\"Certificate appears to be in base64/DER format.\")\n                return True\n\n            self.logger.warning(\"Certificate format not recognized.\")\n            return False\n\n        except Exception as err:\n            self.logger.error(f\"Certificate validation error: {err}\")\n            return False\n\n    def extract_certificate_info(self, certificate: str) -> Dict[str, str]:\n        \"\"\"\n        Extract information from certificate.\n\n        Args:\n            certificate: Certificate data\n\n        Returns:\n            Dictionary containing extracted certificate information\n        \"\"\"\n        self.logger.debug(\"CertificateBusinessLogic.extract_certificate_info()\")\n\n        cert_info = {}\n\n        try:\n            cert_info[\"serial\"] = cert_serial_get(self.logger, certificate)\n            cert_info[\"cn\"] = cert_cn_get(self.logger, certificate)\n            cert_info[\"san\"] = str(cert_san_get(self.logger, certificate))\n            cert_info[\"aki\"] = cert_aki_get(self.logger, certificate)\n\n            # Get certificate dates\n            (issue_uts, expire_uts) = self.calculate_certificate_dates(certificate)\n            cert_info[\"issue_date\"] = issue_uts\n            cert_info[\"expire_date\"] = expire_uts\n\n        except Exception as err:\n            self.logger.error(f\"Certificate info extraction error: {err}\")\n\n        self.logger.debug(\"CertificateBusinessLogic.extract_certificate_info() ended\")\n        return cert_info\n\n    def sanitize_certificate_name(self, certificate_name: str) -> str:\n        \"\"\"\n        Sanitize certificate name for safe database storage.\n\n        Args:\n            certificate_name: Original certificate name\n\n        Returns:\n            Sanitized certificate name\n        \"\"\"\n        try:\n            return string_sanitize(self.logger, certificate_name)\n        except Exception as err:\n            self.logger.error(f\"Certificate name sanitization error: {err}\")\n            return certificate_name\n\n    def format_certificate_response(\n        self, certificate: str, status_code: int = 200\n    ) -> Dict[str, Union[str, int]]:\n        \"\"\"\n        Format certificate for response.\n\n        Args:\n            certificate: Certificate data\n            status_code: HTTP status code\n\n        Returns:\n            Formatted response dictionary\n        \"\"\"\n        self.logger.debug(\"CertificateBusinessLogic.format_certificate_response()\")\n\n        response = {\n            \"code\": status_code,\n            \"data\": certificate if certificate else \"\",\n        }\n\n        if certificate:\n            response[\"headers\"] = {\"Content-Type\": \"application/pem-certificate-chain\"}\n\n        return response\n"
  },
  {
    "path": "acme_srv/certificate_manager.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Certificate Manager - Coordination Layer for Certificate Operations\"\"\"\n# pylint: disable=R0913, R1705\nfrom typing import Dict, List, Tuple, Union, Optional\nfrom acme_srv.certificate_business_logic import CertificateBusinessLogic\nfrom acme_srv.helper import uts_now, b64_url_recode, date_to_uts_utc, uts_to_date_utc\nfrom acme_srv.helpers.certificates import cert_dates_get\n\n# Import will be added when needed to avoid circular imports\n# from acme_srv.certificate import CertificateConfig\n\n\nclass CertificateManager:\n    \"\"\"\n    Coordination Layer for Certificate operations.\n\n    This class orchestrates interactions between CertificateRepository and\n    CertificateBusinessLogic to implement high-level certificate workflows.\n\n    Responsibilities:\n    - Coordinate Repository and BusinessLogic operations\n    - Handle complex workflows that span multiple components\n    - Manage error handling and logging\n    - Process hooks and notifications\n    - Implement transaction-like behavior\n\n    Follows the Manager/Service pattern for workflow coordination.\n    \"\"\"\n\n    def __init__(\n        self,\n        debug: bool = False,\n        logger=None,\n        err_msg_dic=None,\n        repository=None,\n        config=None,\n    ):\n        \"\"\"Initialize the Certificate Manager\"\"\"\n        self.debug = debug\n        self.logger = logger\n        self.err_msg_dic = err_msg_dic or {}\n\n        # Use provided repository\n        self.repository = repository\n        self.business_logic = CertificateBusinessLogic(\n            debug, logger, err_msg_dic, config\n        )\n\n        # Configuration from dataclass or defaults\n        if config:\n            self.cert_operations_log = config.cert_operations_log\n            self.tnauthlist_support = config.tnauthlist_support\n        else:\n            self.cert_operations_log = None\n            self.tnauthlist_support = False\n\n    def search_certificates(\n        self, key: str, value: Union[str, int], vlist: List[str] = None\n    ) -> Dict[str, Union[str, List]]:\n        \"\"\"\n        Search for certificates with business logic validation.\n\n        Args:\n            key: Database field to search by\n            value: Value to search for\n            vlist: Optional list of fields to return\n\n        Returns:\n            Dictionary containing search results and metadata\n        \"\"\"\n        self.logger.debug(f\"CertificateManager.search_certificates({key}={value})\")\n\n        try:\n            # Perform repository search\n            cert_list = self.repository.search_certificates(key, value, vlist)\n\n            # Handle None return from repository (database error)\n            if cert_list is None:\n                result = {\n                    \"certificates\": None,\n                    \"count\": 0,\n                    \"total_found\": 0,\n                    \"error\": \"Database error\",\n                }\n                return result\n\n            # For backward compatibility, don't filter by certificate validation\n            # if we don't have certificate data in the search results\n            if vlist and \"cert\" not in vlist:\n                # If cert field not requested, skip validation\n                processed_results = cert_list\n            else:\n                # Apply business logic processing only when we have cert data\n                processed_results = []\n                for cert in cert_list:\n                    cert_data = cert.get(\"cert\", \"\")\n                    if not cert_data or self.business_logic.validate_certificate_data(\n                        cert_data\n                    ):\n                        processed_results.append(cert)\n\n            result = {\n                \"certificates\": processed_results,\n                \"count\": len(processed_results),\n                \"total_found\": len(cert_list),\n            }\n\n        except Exception as err:\n            self.logger.error(f\"Certificate search error: {err}\")\n            result = {\n                \"certificates\": [],\n                \"count\": 0,\n                \"total_found\": 0,\n                \"error\": str(err),\n            }\n\n        self.logger.debug(\n            f\"CertificateManager.search_certificates() found {result['count']} valid certificates\"\n        )\n        return result\n\n    def get_certificate_info(self, certificate_name: str) -> Dict[str, str]:\n        \"\"\"\n        Get certificate information with validation.\n\n        Args:\n            certificate_name: Name/identifier of the certificate\n\n        Returns:\n            Dictionary containing certificate information\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateManager.get_certificate_info({certificate_name})\"\n        )\n\n        # Sanitize certificate name\n        clean_name = self.business_logic.sanitize_certificate_name(certificate_name)\n\n        # Get certificate from repository\n        cert_info = self.repository.get_certificate_info(clean_name)\n\n        if cert_info and cert_info.get(\"cert\"):\n            # Enhance with business logic extracted info\n            extracted_info = self.business_logic.extract_certificate_info(\n                cert_info[\"cert_raw\"]\n            )\n            cert_info.update(extracted_info)\n\n        return cert_info\n\n    def store_certificate(\n        self,\n        certificate_name: str,\n        csr: str,\n        order_name: str = None,\n        certificate_data: str = None,\n        header_info: str = None,\n    ) -> Tuple[bool, Optional[str]]:\n        \"\"\"\n        Store certificate with full validation workflow.\n\n        Args:\n            certificate_name: Name for the certificate\n            csr: Certificate Signing Request\n            order_name: Associated order name\n            certificate_data: Certificate data if available\n\n        Returns:\n            Tuple of (success, error_message)\n        \"\"\"\n        self.logger.debug(f\"CertificateManager.store_certificate({certificate_name})\")\n\n        try:\n            # Sanitize certificate name\n            certificate_name = self.business_logic.sanitize_certificate_name(\n                certificate_name\n            )\n\n            # Prepare certificate data for storage\n            cert_data = {\n                \"name\": certificate_name,\n            }\n\n            if csr:\n                cert_data[\"csr\"] = csr\n\n            if order_name:\n                cert_data[\"order\"] = order_name\n\n            if header_info:\n                self.logger.debug(\n                    \"CertificateManager.store_certificate(): store header_info with certificate\"\n                )\n                cert_data[\"header_info\"] = header_info\n\n            if certificate_data:\n                cert_data[\"cert\"] = certificate_data\n                cert_data[\"cert_raw\"] = certificate_data\n\n                # Calculate and store certificate dates\n                (\n                    issue_uts,\n                    expire_uts,\n                ) = self.business_logic.calculate_certificate_dates(certificate_data)\n                cert_data[\"issue_uts\"] = issue_uts\n                cert_data[\"expire_uts\"] = expire_uts\n\n            # Store in repository\n            success = self.repository.add_certificate(cert_data)\n\n            if success and self.cert_operations_log and certificate_data:\n                # Log certificate operation\n                self.repository.store_certificate_operation_log(\n                    certificate_name, \"store\", \"success\"\n                )\n\n            return (success, None if success else \"Database storage failed\")\n\n        except Exception as err:\n            self.logger.error(f\"Certificate storage error: {err}\")\n            return (False, str(err))\n\n    def update_certificate_dates(self, certificate_name: str = None) -> Tuple[int, int]:\n        \"\"\"\n        Update certificate issue and expiry dates from certificate data.\n\n        Args:\n            certificate_name: Specific certificate to update, or None for all\n\n        Returns:\n            Tuple of (updated_count, error_count)\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateManager.update_certificate_dates({certificate_name})\"\n        )\n\n        updated_count = 0\n        error_count = 0\n\n        try:\n            if certificate_name:\n                # Update specific certificate\n                cert_list = [self.repository.get_certificate_info(certificate_name)]\n            else:\n                # Get all certificates that need date updates\n                cert_list = self.repository.search_certificates(\n                    \"cert\", \"\", [\"name\", \"cert\"]\n                )\n\n            if not cert_list:\n                self.logger.debug(\"No certificates found for date update\")\n                return (0, 0)\n\n            self.logger.debug(f\"Got {len(cert_list)} certificates to be updated...\")\n\n            for cert in cert_list:\n                if cert and cert.get(\"cert\"):\n                    try:\n                        # Calculate dates using business logic\n                        (\n                            issue_uts,\n                            expire_uts,\n                        ) = self.business_logic.calculate_certificate_dates(\n                            cert[\"cert\"]\n                        )\n\n                        # Update certificate with new dates\n                        update_data = {\n                            \"name\": cert[\"name\"],\n                            \"issue_uts\": issue_uts,\n                            \"expire_uts\": expire_uts,\n                        }\n\n                        if self.repository.update_certificate(update_data):\n                            updated_count += 1\n                        else:\n                            error_count += 1\n\n                    except Exception as err:\n                        self.logger.error(\n                            f\"Error updating dates for certificate {cert.get('name', 'unknown')}: {err}\"\n                        )\n                        error_count += 1\n\n        except Exception as err:\n            self.logger.critical(f\"Certificate dates update failed: {err}\")\n            error_count += 1\n\n        self.logger.debug(\n            f\"CertificateManager.update_certificate_dates() updated {updated_count}, errors {error_count}\"\n        )\n        return (updated_count, error_count)\n\n    def cleanup_certificates(\n        self, timestamp: int = None, purge: bool = False\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"\n        Cleanup expired certificates with business logic validation.\n\n        Args:\n            timestamp: Unix timestamp for cleanup threshold (default: current time)\n            purge: Whether to purge (delete) or just mark for cleanup\n\n        Returns:\n            Tuple of (field_list, report_list) indicating cleanup results\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateManager.cleanup_certificates(timestamp={timestamp}, purge={purge})\"\n        )\n\n        field_list = [\n            \"id\",\n            \"name\",\n            \"expire_uts\",\n            \"issue_uts\",\n            \"cert\",\n            \"cert_raw\",\n            \"csr\",\n            \"created_at\",\n            \"order__id\",\n            \"order__name\",\n        ]\n\n        if not timestamp:\n            timestamp = uts_now()\n\n        try:\n            # Perform cleanup through repository\n            certificate_list = self.repository.search_expired_certificates(\n                timestamp, field_list\n            )\n\n        except Exception as err:\n            self.logger.error(f\"Certificate cleanup error: {err}\")\n            certificate_list = []\n            field_list = []\n\n        report_list = []\n\n        for certificate in certificate_list:\n            try:\n                to_be_cleared = self._check_invalidation(certificate, timestamp, purge)\n\n                if to_be_cleared:\n                    # update report\n                    report_list.append(certificate[\"name\"])\n                    # Update certificate status in repository\n\n                    if purge:\n                        self.repository.delete_certificate(certificate[\"name\"])\n                    else:\n                        data_dic = {\n                            \"name\": certificate[\"name\"],\n                            \"expire_uts\": certificate[\"expire_uts\"],\n                            \"issue_uts\": certificate[\"issue_uts\"],\n                            \"cert\": f\"removed by certificates.cleanup() on {uts_to_date_utc(timestamp)}\",\n                            \"cert_raw\": certificate[\"cert_raw\"],\n                        }\n                        self.repository.add_certificate(data_dic)\n\n            except Exception as err:\n                self.logger.error(\n                    f\"Error processing certificate {certificate.get('name', 'unknown')} during cleanup: {err}\"\n                )\n\n        return (field_list, report_list)\n\n    def _check_invalidation(\n        self, cert: Dict[str, str], timestamp: int, purge: bool = False\n    ):\n        \"\"\"check if cert must be invalidated\"\"\"\n        if \"name\" in cert:\n            self.logger.debug(\"Certificate._check_invalidation(%s)\", cert[\"name\"])\n        else:\n            self.logger.debug(\"Certificate._check_invalidation()\")\n\n        to_be_cleared = False\n\n        if cert and \"name\" in cert:\n            if \"cert\" in cert and cert[\"cert\"] and \"removed by\" in cert[\"cert\"].lower():\n                if purge:\n                    # skip entries which had been cleared before cert[cert] check is needed to cover corner cases\n                    to_be_cleared = True\n\n            elif \"expire_uts\" in cert:\n                # get expiry date from either dictionary or certificate\n                to_be_cleared = self._get_expiredate(cert, timestamp, to_be_cleared)\n            else:\n                # this scneario should never been happen so lets be careful and not clear it\n                to_be_cleared = False\n        else:\n            # entries without a cert-name can be to_be_cleared\n            to_be_cleared = True\n\n        if \"name\" in cert:\n            self.logger.debug(\n                \"Certificate._check_invalidation(%s) ended with %s\",\n                cert[\"name\"],\n                to_be_cleared,\n            )\n        else:\n            self.logger.debug(\n                \"Certificate._check_invalidation() ended with %s\", to_be_cleared\n            )\n\n        return to_be_cleared\n\n    def _assume_expirydate(\n        self, cert: Dict[str, str], timestamp: int, to_be_cleared: bool\n    ) -> bool:\n        \"\"\"assume expiry date\"\"\"\n        self.logger.debug(\"Certificate._assume_expirydate()\")\n\n        if \"csr\" in cert and cert[\"csr\"]:\n            # cover cases for enrollments in flight\n            # we assume that a CSR should turn int a cert within two weeks\n            if \"created_at\" in cert:\n                created_at_uts = date_to_uts_utc(cert[\"created_at\"])\n                if 0 < created_at_uts < timestamp - (14 * 86400):\n                    to_be_cleared = True\n            else:\n                # this scneario should never been happen so lets be careful and not clear it\n                to_be_cleared = False\n        else:\n            # no csr and no cert - to be cleared\n            to_be_cleared = True\n\n        self.logger.debug(\"Certificate._assume_expirydate() ended\")\n        return to_be_cleared\n\n    def _get_expiredate(\n        self, cert: Dict[str, str], timestamp: int, to_be_cleared: bool\n    ) -> bool:\n        \"\"\"get expirey date from certificate\"\"\"\n        self.logger.debug(\"Certificate._get_expiredate()\")\n        # in case cert_expiry in table is 0 try to get it from cert\n        if cert[\"expire_uts\"] == 0:\n            if \"cert_raw\" in cert and cert[\"cert_raw\"]:\n                # get expiration from certificate\n                (issue_uts, expire_uts) = cert_dates_get(self.logger, cert[\"cert_raw\"])\n                if 0 < expire_uts < timestamp:\n                    # returned date is other than 0 and lower than given timestamp\n                    cert[\"issue_uts\"] = issue_uts\n                    cert[\"expire_uts\"] = expire_uts\n                    to_be_cleared = True\n            else:\n                to_be_cleared = self._assume_expirydate(cert, timestamp, to_be_cleared)\n        else:\n            # expired based on expire_uts from db\n            to_be_cleared = True\n\n        self.logger.debug(\n            \"Certificate._expiredate_get() ended with: to_be_cleared:  %s\",\n            to_be_cleared,\n        )\n        return to_be_cleared\n\n    def check_account_authorization(\n        self, account_name: str, certificate: str\n    ) -> Dict[str, str]:\n        \"\"\"\n        Check if account has authorization for certificate.\n\n        Args:\n            account_name: Name of the account\n            certificate: Certificate data\n\n        Returns:\n            Dictionary containing authorization check result\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateManager.check_account_authorization({account_name})\"\n        )\n\n        try:\n            # Encode certificate for database lookup\n            encoded_cert = b64_url_recode(self.logger, certificate)\n\n            # Check authorization through repository\n            result = self.repository.get_account_check_result(\n                account_name, encoded_cert\n            )\n\n            if result:\n                return {\"status\": \"authorized\", \"account\": account_name}\n            else:\n                return {\n                    \"status\": \"unauthorized\",\n                    \"error\": \"Account not authorized for this certificate\",\n                }\n\n        except Exception as err:\n            self.logger.error(f\"Account authorization check error: {err}\")\n            return {\"status\": \"error\", \"error\": str(err)}\n\n    def prepare_certificate_response(\n        self, certificate: str, status_code: int = 200\n    ) -> Dict[str, Union[str, int]]:\n        \"\"\"\n        Prepare certificate response with proper formatting.\n\n        Args:\n            certificate: Certificate data\n            status_code: HTTP status code\n\n        Returns:\n            Formatted response dictionary\n        \"\"\"\n        self.logger.debug(\"CertificateManager.prepare_certificate_response()\")\n\n        return self.business_logic.format_certificate_response(certificate, status_code)\n\n    def update_order_status(\n        self, order_name: str, status: str, certificate_name: str = None\n    ) -> bool:\n        \"\"\"\n        Update order status with certificate association.\n\n        Args:\n            order_name: Name of the order to update\n            status: New status for the order\n            certificate_name: Associated certificate name\n\n        Returns:\n            True if successful, False otherwise\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateManager.update_order_status({order_name}, {status})\"\n        )\n\n        try:\n            order_data = {\"name\": order_name, \"status\": status}\n\n            if certificate_name:\n                order_data[\"certificate\"] = certificate_name\n\n            return self.repository.update_order(order_data)\n\n        except Exception as err:\n            self.logger.error(f\"Order status update error: {err}\")\n            return False\n\n    def get_certificate_by_order(self, order_name: str) -> Dict[str, str]:\n        \"\"\"\n        Get certificate information by order name.\n\n        Args:\n            order_name: Name of the order\n\n        Returns:\n            Certificate information dictionary\n        \"\"\"\n        self.logger.debug(f\"CertificateManager.get_certificate_by_order({order_name})\")\n\n        try:\n            cert_info = self.repository.get_certificate_by_order(order_name)\n\n            if cert_info and cert_info.get(\"cert\"):\n                # Enhance with business logic extracted info\n                extracted_info = self.business_logic.extract_certificate_info(\n                    cert_info[\"cert\"]\n                )\n                cert_info.update(extracted_info)\n\n            return cert_info\n\n        except Exception as err:\n            self.logger.error(f\"Get certificate by order error: {err}\")\n            return {}\n\n    def validate_and_store_csr(\n        self, order_name: str, csr: str, header_info: str = None\n    ) -> Tuple[bool, str]:\n        \"\"\"\n        Validate CSR and store it with generated certificate name.\n\n        Args:\n            order_name: Associated order name\n            csr: Certificate Signing Request\n            header_info: Optional header information\n\n        Returns:\n            Tuple of (success, certificate_name)\n        \"\"\"\n        self.logger.debug(f\"CertificateManager.validate_and_store_csr({order_name})\")\n\n        try:\n            # Validate CSR\n            (code, error, _detail) = self.business_logic.validate_csr(csr)\n            if code != 200:\n                self.logger.error(f\"CSR validation failed: {error}\")\n                return (False, \"\")\n\n            # Generate certificate name\n            certificate_name = self.business_logic.generate_certificate_name()\n\n            # Store certificate with CSR\n            (success, error_msg) = self.store_certificate(\n                certificate_name, csr, order_name, header_info=header_info\n            )\n\n            if success:\n                return (True, certificate_name)\n            else:\n                self.logger.error(f\"CSR storage failed: {error_msg}\")\n                return (False, certificate_name)  # Return name even on failure\n\n        except Exception as err:\n            self.logger.error(f\"CSR validation and storage error: {err}\")\n            # Generate name even on error for consistency\n            certificate_name = self.business_logic.generate_certificate_name()\n            return (False, certificate_name)\n"
  },
  {
    "path": "acme_srv/certificate_repository.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Certificate Repository - Database operations abstraction\"\"\"\n\nfrom abc import ABC, abstractmethod\nfrom typing import Dict, List, Any, Optional, Union, Tuple\nfrom acme_srv.db_handler import DBstore\n\n\nclass CertificateRepository(ABC):\n    \"\"\"Abstract base class for certificate repository operations.\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    @abstractmethod\n    def search_certificates(\n        self, key: str, value: Union[str, int], vlist: List[str] = None\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Search for certificates matching criteria.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def get_certificate_info(self, certificate_name: str) -> Dict[str, str]:\n        \"\"\"Get certificate information by name.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def search_expired_certificates(\n        self, timestamp: int, field_list: List[str] = None\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Cleanup old certificates.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def certificate_account_check(\n        self, account_name: str, certificate: str\n    ) -> Dict[str, str]:\n        \"\"\"Check account for certificate.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def certificate_lookup(\n        self, key: str, value: str, vlist: List[str] = None\n    ) -> Dict[str, Any]:\n        \"\"\"Lookup certificate by key/value.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def certificate_add(self, data_dic: Dict[str, Any]) -> int:\n        \"\"\"Add certificate to database.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def certificate_delete(self, key: str, value: Any) -> bool:\n        \"\"\"Delete certificate from database.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def order_lookup(\n        self, key: str, value: str, vlist: List[str] = None\n    ) -> Dict[str, Any]:\n        \"\"\"Lookup order by key/value.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def order_update(self, data_dic: Dict[str, Any]) -> bool:\n        \"\"\"Update order in database.\"\"\"\n        pass  # pragma: no cover\n\n\nclass DatabaseCertificateRepository(CertificateRepository):\n    \"\"\"Database implementation of certificate repository.\"\"\"\n\n    def __init__(self, dbstore: DBstore, logger):\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def search_certificates(\n        self, key: str, value: Union[str, int], vlist: List[str] = None\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Search certificates in the database.\n\n        Args:\n            key: Database field to search by\n            value: Value to search for\n            vlist: Optional list of fields to return\n\n        Returns:\n            List of certificate dictionaries matching the search criteria\n        \"\"\"\n        self.logger.debug(f\"CertificateRepository.search_certificates({key}={value})\")\n\n        try:\n            if vlist:\n                cert_list = self.dbstore.certificates_search(key, value, vlist)\n            else:\n                cert_list = self.dbstore.certificates_search(key, value)\n\n            if not cert_list:\n                cert_list = []\n\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate search: {err}\")\n            cert_list = None\n\n        if cert_list is not None:\n            self.logger.debug(\n                f\"CertificateRepository.search_certificates() found {len(cert_list)} certificates\"\n            )\n        else:\n            self.logger.debug(\n                \"CertificateRepository.search_certificates() returned None due to database error\"\n            )\n        return cert_list\n\n    def get_certificate_info(self, certificate_name: str) -> Dict[str, str]:\n        \"\"\"\n        Get certificate information from database.\n\n        Args:\n            certificate_name: Name/identifier of the certificate\n\n        Returns:\n            Dictionary containing certificate information\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.get_certificate_info({certificate_name})\"\n        )\n\n        try:\n            cert_info = self.dbstore.certificate_lookup(\n                \"name\",\n                certificate_name,\n                (\"name\", \"csr\", \"cert_raw\", \"cert\", \"order__name\", \"order__status_id\"),\n            )\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate lookup: {err}\")\n            cert_info = {}\n\n        if cert_info is not None and hasattr(cert_info, \"__len__\"):\n            self.logger.debug(\n                f\"CertificateRepository.get_certificate_info() returned {len(cert_info)} fields\"\n            )\n        else:\n            self.logger.debug(\n                \"CertificateRepository.get_certificate_info() returned non-iterable or None result\"\n            )\n\n        return cert_info\n\n    def add_certificate(self, data_dic: Dict[str, str]) -> bool:\n        \"\"\"\n        Add a new certificate to the database.\n\n        Args:\n            data_dic: Dictionary containing certificate data\n\n        Returns:\n            True if successful, False otherwise\n        \"\"\"\n        self.logger.debug(\"CertificateRepository.add_certificate()\")\n\n        try:\n            result = self.dbstore.certificate_add(data_dic)\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate add: {err}\")\n            result = False\n\n        self.logger.debug(f\"CertificateRepository.add_certificate() result: {result}\")\n        return result\n\n    def delete_certificate(self, certificate_name: str) -> bool:\n        \"\"\"\n        Delete a certificate from the database.\n\n        Args:\n            certificate_name: Name/identifier of the certificate to delete\n\n        Returns:\n            True if successful, False otherwise\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.delete_certificate({certificate_name})\"\n        )\n\n        try:\n            result = self.dbstore.certificate_delete(\"name\", certificate_name)\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate delete: {err}\")\n            result = False\n\n        self.logger.debug(\n            f\"CertificateRepository.delete_certificate() result: {result}\"\n        )\n        return result\n\n    def get_account_check_result(\n        self, account_name: str, certificate: str\n    ) -> Optional[Dict[str, str]]:\n        \"\"\"\n        Check if account has access to certificate.\n\n        Args:\n            account_name: Name of the account\n            certificate: Certificate data\n\n        Returns:\n            Account check result or None if error\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.get_account_check_result({account_name})\"\n        )\n\n        try:\n            result = self.dbstore.certificate_account_check(account_name, certificate)\n        except Exception as err:\n            self.logger.critical(f\"Database error during account check: {err}\")\n            result = None\n\n        return result\n\n    def update_order(self, data_dic: Dict[str, str]) -> bool:\n        \"\"\"\n        Update order information in database.\n\n        Args:\n            data_dic: Dictionary containing order data to update\n\n        Returns:\n            True if successful, False otherwise\n        \"\"\"\n        self.logger.debug(\"CertificateRepository.update_order()\")\n\n        try:\n            self.dbstore.order_update(data_dic)\n            result = True\n        except Exception as err:\n            self.logger.critical(f\"Database error during order update: {err}\")\n            result = False\n\n        return result\n\n    def get_orders_by_account(self, account_name: str) -> List[Dict[str, str]]:\n        \"\"\"\n        Get all orders for a specific account.\n\n        Args:\n            account_name: Name of the account\n\n        Returns:\n            List of order dictionaries\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.get_orders_by_account({account_name})\"\n        )\n\n        try:\n            orders = self.dbstore.orders_search(\"account\", account_name)\n            if not orders:\n                orders = []\n        except Exception as err:\n            self.logger.critical(f\"Database error during orders search: {err}\")\n            orders = []\n\n        return orders\n\n    def search_expired_certificates(\n        self, timestamp: int, field_list: List[str] = None\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"\n        Cleanup certificates based on timestamp and purge flag.\n\n        Args:\n            timestamp: Unix timestamp for cleanup threshold\n            purge: Whether to purge (delete) or just mark for cleanup\n\n        Returns:\n            Tuple of (field_list, report_list) indicating cleanup results\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.search_expired_certificates(timestamp={timestamp}\"\n        )\n\n        # get expired certificates\n        try:\n            certificate_list = self.dbstore.certificates_search(\n                \"expire_uts\", timestamp, field_list, \"<=\"\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to search for certificates to clean up: %s\",\n                err_,\n            )\n            certificate_list = []\n\n        self.logger.debug(\n            f\"CertificateRepository.search_expired_certificates() processed {len(certificate_list)} certificates\"\n        )\n        return certificate_list\n\n    def get_certificate_by_order(self, order_name: str) -> Dict[str, str]:\n        \"\"\"\n        Get certificate associated with an order.\n\n        Args:\n            order_name: Name of the order\n\n        Returns:\n            Certificate information dictionary\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.get_certificate_by_order({order_name})\"\n        )\n\n        try:\n            cert_info = self.dbstore.certificate_lookup(\"order__name\", order_name)\n        except Exception as err:\n            self.logger.critical(\n                f\"Database error during certificate lookup by order: {err}\"\n            )\n            cert_info = {}\n\n        return cert_info\n\n    def store_certificate_operation_log(\n        self, certificate_name: str, operation: str, result: str\n    ) -> bool:\n        \"\"\"\n        Store certificate operation log entry.\n\n        Args:\n            certificate_name: Name of the certificate\n            operation: Type of operation performed\n            result: Result of the operation\n\n        Returns:\n            True if successful, False otherwise\n        \"\"\"\n        self.logger.debug(\n            f\"CertificateRepository.store_certificate_operation_log({certificate_name}, {operation})\"\n        )\n\n        try:\n            log_data = {\n                \"certificate\": certificate_name,\n                \"operation\": operation,\n                \"result\": result,\n            }\n            result = self.dbstore.cahandler_add(log_data)\n        except Exception as err:\n            self.logger.critical(\n                f\"Database error during certificate operation log: {err}\"\n            )\n            result = False\n\n        return result\n\n    def certificate_account_check(\n        self, account_name: str, certificate: str\n    ) -> Dict[str, str]:\n        \"\"\"Check account for certificate.\"\"\"\n        self.logger.debug(\n            f\"DatabaseCertificateRepository.certificate_account_check({account_name})\"\n        )\n\n        try:\n            result = self.dbstore.certificate_account_check(account_name, certificate)\n        except Exception as err:\n            self.logger.critical(\n                f\"Database error during certificate account check: {err}\"\n            )\n            result = None\n\n        return result\n\n    def certificate_lookup(\n        self, key: str, value: str, vlist: List[str] = None\n    ) -> Dict[str, Any]:\n        \"\"\"Lookup certificate by key/value.\"\"\"\n        self.logger.debug(\n            f\"DatabaseCertificateRepository.certificate_lookup({key}={value})\"\n        )\n\n        try:\n            if vlist:\n                result = self.dbstore.certificate_lookup(key, value, vlist)\n            else:\n                result = self.dbstore.certificate_lookup(key, value)\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate lookup: {err}\")\n            result = {}\n\n        return result\n\n    def certificate_add(self, data_dic: Dict[str, Any]) -> int:\n        \"\"\"Add certificate to database.\"\"\"\n        self.logger.debug(\n            f\"DatabaseCertificateRepository.certificate_add({data_dic.get('name', 'unknown')})\"\n        )\n\n        try:\n            result = self.dbstore.certificate_add(data_dic)\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate add: {err}\")\n            result = None\n\n        return result\n\n    def certificate_delete(self, key: str, value: Any) -> bool:\n        \"\"\"Delete certificate from database.\"\"\"\n        self.logger.debug(\n            f\"DatabaseCertificateRepository.certificate_delete({key}={value})\"\n        )\n\n        try:\n            result = self.dbstore.certificate_delete(key, value)\n        except Exception as err:\n            self.logger.critical(f\"Database error during certificate delete: {err}\")\n            result = False\n\n        return result\n\n    def order_lookup(\n        self, key: str, value: str, vlist: List[str] = None\n    ) -> Dict[str, Any]:\n        \"\"\"Lookup order by key/value.\"\"\"\n        self.logger.debug(f\"DatabaseCertificateRepository.order_lookup({key}={value})\")\n\n        try:\n            if vlist:\n                result = self.dbstore.order_lookup(key, value, vlist)\n            else:\n                result = self.dbstore.order_lookup(key, value)\n        except Exception as err:\n            self.logger.critical(f\"Database error during order lookup: {err}\")\n            result = {}\n\n        return result\n\n    def order_update(self, data_dic: Dict[str, Any]) -> bool:\n        \"\"\"Update order in database.\"\"\"\n        self.logger.debug(\n            f\"DatabaseCertificateRepository.order_update({data_dic.get('name', 'unknown')})\"\n        )\n\n        try:\n            result = self.dbstore.order_update(data_dic)\n        except Exception as err:\n            self.logger.critical(f\"Database error during order update: {err}\")\n            result = False\n\n        return result\n"
  },
  {
    "path": "acme_srv/challenge.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=r0902, r0912, r0913, r0915, r1705\n\"\"\"Challenge class - refactored version\"\"\"\nimport json\nimport time\nfrom typing import List, Tuple, Dict, Optional, Any\nfrom dataclasses import dataclass\nfrom threading import Thread\nfrom acme_srv.helper import (\n    generate_random_string,\n    jwk_thumbprint_get,\n    parse_url,\n    uts_now,\n    uts_to_date_utc,\n    load_config,\n    error_dic_get,\n    config_eab_profile_load,\n    config_async_mode_load,\n)\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.message import Message\n\n# Import our modules\nfrom acme_srv.challenge_validators import (\n    ChallengeContext,\n    ValidationResult,\n)\nfrom acme_srv.challenge_registry_setup import create_challenge_validator_registry\n\nfrom acme_srv.challenge_business_logic import (\n    ChallengeRepository,\n    ChallengeStateManager,\n    ChallengeFactory,\n    ChallengeService,\n    ChallengeInfo,\n    ChallengeCreationRequest,\n    ChallengeUpdateRequest,\n)\nfrom acme_srv.challenge_error_handling import (\n    ErrorHandler,\n    ValidationError,\n    DatabaseError,\n    UnsupportedChallengeTypeError,\n)\n\n\n@dataclass\nclass ChallengeConfiguration:\n    \"\"\"Configuration for challenge processing.\"\"\"\n\n    validation_disabled: bool = False\n    validation_timeout: int = 10\n    dns_server_list: Optional[List[str]] = None\n    dns_validation_pause_timer: float = 0.5\n    proxy_server_list: Optional[Dict[str, str]] = None\n    sectigo_sim: bool = False\n    tnauthlist_support: bool = False\n    email_identifier_support: bool = False\n    email_address: Optional[str] = None\n    forward_address_check: bool = False\n    reverse_address_check: bool = False\n    source_address: Optional[str] = None\n    eab_profiling: bool = False\n    eab_handler: Optional[Any] = None\n    async_mode: bool = False\n\n\nclass DatabaseChallengeRepository(ChallengeRepository):\n    \"\"\"Database implementation of challenge repository.\"\"\"\n\n    def __init__(self, dbstore: DBstore, logger, expiry: float = 3600):\n        self.dbstore = dbstore\n        self.logger = logger\n        self.expiry = expiry\n\n    def find_challenges_by_authorization(\n        self, authorization_name: str\n    ) -> List[ChallengeInfo]:\n        \"\"\"Find all challenges for a given authorization.\"\"\"\n        self.logger.debug(\n            \"DatabaseChallengeRepository.find_challenges_by_authorization(%s)\",\n            authorization_name,\n        )\n        try:\n            challenge_list = self.dbstore.challenges_search(\n                \"authorization__name\",\n                authorization_name,\n                (\"name\", \"type\", \"status__name\", \"token\", \"validation_error\"),\n            )\n\n            result = []\n            for challenge in challenge_list:\n                challenge_info = ChallengeInfo(\n                    name=challenge[\"name\"],\n                    type=challenge[\"type\"],\n                    token=challenge[\"token\"],\n                    status=challenge.get(\"status__name\", \"pending\"),\n                    authorization_name=authorization_name,\n                    authorization_type=\"\",  # Would need additional query\n                    authorization_value=\"\",  # Would need additional query\n                    url=\"\",  # Will be constructed later\n                    validation_error=challenge.get(\"validation_error\", None),\n                )\n                result.append(challenge_info)\n\n            self.logger.debug(\n                \"DatabaseChallengeRepository.find_challenges_by_authorization() ended: found %d challenges\",\n                len(result),\n            )\n            return result\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to search for challenges: %s\", err\n            )\n            raise DatabaseError(f\"Failed to search challenges: {err}\") from err\n\n    def get_challengeinfo_by_challengename(\n        self, name: str, vlist: Optional[List[str]] = (\"name\", \"type\", \"status__name\")\n    ) -> Optional[str]:\n        \"\"\"Get challenge information challenge name.\"\"\"\n        self.logger.debug(\n            \"DatabaseChallengeRepository.get_challengeinfo_by_challengename(%s)\", name\n        )\n        try:\n            challenge_dic = self.dbstore.challenge_lookup(\n                \"name\",\n                name,\n                vlist=vlist,\n            )\n\n            self.logger.debug(\n                \"DatabaseChallengeRepository.get_challengeinfo_by_challengename() ended: found challenge %s\",\n                challenge_dic,\n            )\n            if not challenge_dic:\n                return None\n            return challenge_dic\n\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to lookup challenge keyauthorization: %s\", err\n            )\n            raise DatabaseError(\n                f\"Failed to lookup challenge keyauthorization: {err}\"\n            ) from err\n\n    def get_challenge_by_name(\n        self, name: str, vlist: Optional[List[str]] = None\n    ) -> Optional[ChallengeInfo]:\n        \"\"\"Get challenge information by name.\"\"\"\n        self.logger.debug(\"DatabaseChallengeRepository.get_challenge_by_name(%s)\", name)\n        try:\n            challenge_dic = self.dbstore.challenge_lookup(\n                \"name\",\n                name,\n                vlist=(\n                    \"type\",\n                    \"token\",\n                    \"status__name\",\n                    \"authorization__name\",\n                    \"authorization__type\",\n                    \"authorization__value\",\n                    \"validated\",\n                    \"validation_error\",\n                ),\n            )\n\n            self.logger.debug(\n                \"DatabaseChallengeRepository.get_challenge_by_name() ended: found challenge %s\",\n                challenge_dic,\n            )\n\n            if not challenge_dic:\n                return None\n            return ChallengeInfo(\n                name=name,\n                type=challenge_dic.get(\"type\", \"\"),\n                token=challenge_dic.get(\"token\", \"\"),\n                status=challenge_dic.get(\"status\", \"pending\"),\n                authorization_name=challenge_dic.get(\"authorization\", \"\"),\n                authorization_type=challenge_dic.get(\"authorization__type\", \"\"),\n                authorization_value=challenge_dic.get(\"authorization__value\", \"\"),\n                url=\"\",  # Will be constructed later\n                validated=uts_to_date_utc(challenge_dic.get(\"validated\"))\n                if challenge_dic.get(\"status\") == \"valid\"\n                else None,\n                validation_error=challenge_dic.get(\"validation_error\", None)\n                if \"validation_error\" in challenge_dic\n                else None,\n            )\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to lookup challenge: %s\", err)\n            raise DatabaseError(f\"Failed to lookup challenge: {err}\") from err\n\n    def create_challenge(self, request: ChallengeCreationRequest) -> Optional[str]:\n        \"\"\"Create a new challenge and return its name.\"\"\"\n        self.logger.debug(\"DatabaseChallengeRepository.create_challenge(%s)\", request)\n        try:\n            challenge_name = generate_random_string(self.logger, 12)\n            data_dic = {\n                \"name\": challenge_name,\n                \"expires\": uts_now() + self.expiry,\n                # \"expires\": request.expires if request.expires else uts_now() + self.expiry,\n                \"type\": request.challenge_type,\n                \"token\": request.token,\n                \"authorization\": request.authorization_name,\n                \"status\": 2,  # pending\n            }\n\n            # Handle special challenge types\n            if request.challenge_type == \"email-reply-00\":\n                token1 = generate_random_string(self.logger, 32)\n                data_dic[\"keyauthorization\"] = token1\n                # Email sending would be handled elsewhere\n            elif request.challenge_type == \"sectigo-email-01\":\n                data_dic[\"status\"] = 5  # valid\n\n            chid = self.dbstore.challenge_add(\n                request.value, request.challenge_type, data_dic\n            )\n            self.logger.debug(\n                \"DatabaseChallengeRepository.create_challenge() ended: created challenge %s/%s\",\n                challenge_name,\n                chid,\n            )\n            return challenge_name if chid else None\n\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to add new challenge: %s\", err)\n            raise DatabaseError(f\"Failed to create challenge: {err}\") from err\n\n    def update_challenge(self, request: ChallengeUpdateRequest) -> bool:\n        \"\"\"Update an existing challenge.\"\"\"\n        self.logger.debug(\n            \"DatabaseChallengeRepository.update_challenge(%s)\", request.name\n        )\n        try:\n            data_dic = {\"name\": request.name}\n\n            if request.status:\n                data_dic[\"status\"] = request.status\n            if request.source:\n                data_dic[\"source\"] = request.source\n            if request.validated:\n                data_dic[\"validated\"] = request.validated\n            if request.keyauthorization:\n                data_dic[\"keyauthorization\"] = request.keyauthorization\n            if request.validation_error:\n                data_dic[\"validation_error\"] = request.validation_error\n            self.dbstore.challenge_update(data_dic)\n            self.logger.debug(\n                \"DatabaseChallengeRepository.update_challenge() ended: updated challenge %s\",\n                request.name,\n            )\n            return True\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to update challenge: %s\", err)\n            raise DatabaseError(f\"Failed to update challenge: {err}\") from err\n\n    def update_authorization_status(self, challenge_name: str, status: str) -> bool:\n        \"\"\"Update authorization status based on challenge.\"\"\"\n        self.logger.debug(\n            \"DatabaseChallengeRepository.update_authorization_status(%s, %s)\",\n            challenge_name,\n            status,\n        )\n        try:\n            result = False\n            # Get authorization name from challenge\n            authz_info = self.dbstore.challenge_lookup(\n                \"name\", challenge_name, [\"authorization__name\"]\n            )\n\n            if authz_info and \"authorization\" in authz_info:\n                data_dic = {\"name\": authz_info[\"authorization\"], \"status\": status}\n                self.dbstore.authorization_update(data_dic)\n                result = True\n\n            self.logger.debug(\n                \"DatabaseChallengeRepository.update_authorization_status() ended: updated authorization for challenge %s/%s\",\n                challenge_name,\n                result,\n            )\n            return result\n\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to update authorization: %s\", err\n            )\n            raise DatabaseError(f\"Failed to update authorization: {err}\") from err\n\n    def get_account_jwk(self, challenge_name: str) -> Optional[Dict[str, Any]]:\n        \"\"\"Get JWK for the account associated with the challenge.\"\"\"\n        self.logger.debug(\n            \"DatabaseChallengeRepository.get_account_jwk(%s)\", challenge_name\n        )\n        try:\n            challenge_dic = self.dbstore.challenge_lookup(\n                \"name\", challenge_name, [\"authorization__order__account__name\"]\n            )\n            result = None\n            if challenge_dic and \"authorization__order__account__name\" in challenge_dic:\n                result = self.dbstore.jwk_load(\n                    challenge_dic[\"authorization__order__account__name\"]\n                )\n            self.logger.debug(\n                \"DatabaseChallengeRepository.get_account_jwk() ended: retrieved JWK for challenge %s/%s\",\n                challenge_name,\n                result,\n            )\n            return result\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to get account JWK: %s\", err)\n            raise DatabaseError(f\"Failed to get account JWK: {err}\") from err\n\n\nclass Challenge:\n    \"\"\"Challenge Class\"\"\"\n\n    def __init__(\n        self,\n        debug: bool = False,\n        srv_name: str = None,\n        logger=None,\n        source: str = None,\n        expiry: int = 3600,\n    ):\n        \"\"\"Initialize the challenge handler.\"\"\"\n        self.logger = logger\n        self.config = ChallengeConfiguration()\n        self.expiry = expiry\n        self.server_name = srv_name\n        self.path_dic = {\"chall_path\": \"/acme/chall/\", \"authz_path\": \"/acme/authz/\"}\n        self.source_address = source\n\n        # Initialize core components\n        self.dbstore = DBstore(debug, self.logger)\n        self.message = Message(debug, self.server_name, self.logger)\n\n        # Initialize error message dictionary for error responses\n        self.err_msg_dic = error_dic_get(self.logger)\n        # Initialize error handler\n        self.error_handler = ErrorHandler(self.logger)\n\n        # class containing all database operations\n        self.repository = DatabaseChallengeRepository(self.dbstore, self.logger)\n        # class managing challenge state transitions\n        self.state_manager = ChallengeStateManager(self.repository, self.logger)\n\n        # These will be initialized after configuration is loaded\n        self.factory = None\n        self.service = None\n\n        # Initialize validation components\n        self.validator_registry = None\n\n        self.proxy_server_list = None\n\n    def __enter__(self):\n        \"\"\"Context manager entry.\"\"\"\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Context manager exit.\"\"\"\n        # pylint: disable=unnecessary-pass\n        pass\n\n    def _create_error_response(\n        self, code: int, message: str, detail: str\n    ) -> Dict[str, str]:\n        \"\"\"Create standardized error response.\"\"\"\n        self.logger.debug(\"Challenge._create_error_response() called\")\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        return self.message.prepare_response({}, status_dic)\n\n    def _create_success_response(self, response_dic: Dict[str, Any]) -> Dict[str, str]:\n        \"\"\"Create standardized success response.\"\"\"\n        self.logger.debug(\"Challenge._create_success_response() called\")\n        status_dic = {\"code\": 200, \"type\": None, \"detail\": None}\n        return self.message.prepare_response(response_dic, status_dic)\n\n    def _execute_challenge_validation(self, challenge_name: str) -> ValidationResult:\n        \"\"\"Execute challenge validation using registry.\"\"\"\n        self.logger.debug(\"Challenge._execute_challenge_validation(%s)\", challenge_name)\n\n        # Get challenge details for validation\n        challenge_details = self._get_challenge_validation_details(challenge_name)\n        if not challenge_details:\n            raise ValidationError(\"Could not retrieve challenge details for validation\")\n\n        challenge_type = challenge_details[\"type\"]\n\n        # Check if challenge type is supported\n        if not self.validator_registry.is_supported(challenge_type):\n            raise UnsupportedChallengeTypeError(\n                challenge_type, self.validator_registry.get_supported_types()\n            )\n\n        # Create validation context\n        context = ChallengeContext(\n            challenge_name=challenge_name,\n            token=challenge_details[\"token\"],\n            jwk_thumbprint=challenge_details[\"jwk_thumbprint\"],\n            keyauthorization=challenge_details[\"keyauthorization\"],\n            authorization_type=challenge_details[\"authorization_type\"],\n            authorization_value=challenge_details[\"authorization_value\"],\n            dns_servers=self.config.dns_server_list,\n            proxy_servers=self.config.proxy_server_list,\n            timeout=self.config.validation_timeout,\n        )\n\n        # Perform validation with retry logic for DNS challenges\n        return self._perform_validation_with_retry(challenge_type, context)\n\n    def _extract_challenge_name_from_url(self, url: str) -> str:\n        \"\"\"Extract challenge name from URL.\"\"\"\n        self.logger.debug(\"Challenge._extract_challenge_name_from_url(%s)\", url)\n        url_dic = parse_url(self.logger, url)\n        challenge_name = url_dic[\"path\"].replace(self.path_dic[\"chall_path\"], \"\")\n        if \"/\" in challenge_name:\n            (challenge_name, _suffix) = challenge_name.split(\"/\", 1)\n        return challenge_name\n\n    def _get_challenge_validation_details(\n        self, challenge_name: str\n    ) -> Optional[Dict[str, str]]:\n        \"\"\"Get all details needed for challenge validation.\"\"\"\n        self.logger.debug(\n            \"Challenge._get_challenge_validation_details(%s)\", challenge_name\n        )\n        try:\n            challenge_dic = self.dbstore.challenge_lookup(\n                \"name\",\n                challenge_name,\n                [\n                    \"type\",\n                    \"status__name\",\n                    \"token\",\n                    \"keyauthorization\",\n                    \"authorization__name\",\n                    \"authorization__type\",\n                    \"authorization__value\",\n                    \"authorization__token\",\n                    \"authorization__order__account__name\",\n                ],\n            )\n\n            if not challenge_dic:\n                return None\n\n            # Get JWK for thumbprint calculation\n            pub_key = self.repository.get_account_jwk(challenge_name)\n            if not pub_key:\n                return None\n\n            jwk_thumbprint = jwk_thumbprint_get(self.logger, pub_key)\n            self.logger.debug(\"Challenge._get_challenge_validation_details() ended\")\n            return {\n                \"type\": challenge_dic[\"type\"],\n                \"token\": challenge_dic[\"token\"],\n                \"authorization_type\": challenge_dic[\"authorization__type\"],\n                \"authorization_value\": challenge_dic[\"authorization__value\"],\n                \"jwk_thumbprint\": jwk_thumbprint,\n                \"keyauthorization\": challenge_dic[\"keyauthorization\"],\n            }\n\n        except Exception as err:\n            self.logger.error(\"Failed to get challenge validation details: %s\", err)\n            self.logger.debug(\n                \"Challenge._get_challenge_validation_details() ended with error\"\n            )\n            return None\n\n    def _handle_challenge_validation_request(\n        self,\n        _code: int,\n        payload: Dict[str, str],\n        protected: Dict[str, str],\n        challenge_name: str,\n        challenge_info: ChallengeInfo,\n    ) -> Dict[str, str]:\n        \"\"\"Handle challenge validation request with improved flow.\"\"\"\n        self.logger.debug(\n            \"Challenge._handle_challenge_validation_request(%s)\", challenge_name\n        )\n        # Check tnauthlist payload if needed\n        if self.config.tnauthlist_support:\n            tnauthlist_result = self._validate_tnauthlist_payload(\n                payload, challenge_info\n            )\n            if tnauthlist_result[\"code\"] != 200:\n                return tnauthlist_result\n\n        # Start validation if challenge is not already valid or processing\n        if challenge_info.status not in (\"valid\", \"processing\"):\n            self._start_async_validation(challenge_name, payload)\n\n        # Get updated challenge info\n        updated_challenge_info = self.repository.get_challenge_by_name(challenge_name)\n\n        # Prepare response\n        response_dic = {\n            \"data\": {\n                \"type\": updated_challenge_info.type,\n                \"status\": updated_challenge_info.status,\n                \"token\": updated_challenge_info.token,\n                \"url\": protected[\"url\"],\n            },\n            \"header\": {\n                \"Link\": f'<{self.server_name}{self.path_dic[\"authz_path\"]}{updated_challenge_info.authorization_name}>;rel=\"up\"'\n            },\n        }\n\n        # address a few cornercases\n        if updated_challenge_info.validation_error:\n            # add validation error in response for failed challenges\n            try:\n                response_dic[\"data\"][\"error\"] = json.loads(\n                    updated_challenge_info.validation_error\n                )\n            except Exception:\n                response_dic[\"data\"][\"error\"] = {\n                    \"status\": 400,\n                    \"type\": \"urn:ietf:params:acme:error:unknown\",\n                    \"detail\": updated_challenge_info.validation_error,\n                }\n\n        if (\n            updated_challenge_info.type == \"email-reply-00\"\n            and self.config.email_address\n        ):\n            # add from address in response for email challenges\n            response_dic[\"data\"][\"from\"] = self.config.email_address\n\n        if (\n            updated_challenge_info.validated\n            and updated_challenge_info.status == \"valid\"\n        ):\n            # add validated flag if challenge is valid\n            response_dic[\"data\"][\"validated\"] = updated_challenge_info.validated\n\n        self.logger.debug(\"Challenge._handle_challenge_validation_request() ended\")\n        return self._create_success_response(response_dic)\n\n    def _handle_validation_disabled(self, challenge_name: str) -> bool:\n        \"\"\"Handle validation when it's disabled.\"\"\"\n        self.logger.debug(\"Challenge._handle_validation_disabled(%s)\", challenge_name)\n        if self.config.forward_address_check or self.config.reverse_address_check:\n            # Perform source address checks even when validation is disabled\n            (\n                challenge_check,\n                invalid,\n                error_message,\n            ) = self._perform_source_address_validation(challenge_name)\n        else:\n            self.logger.warning(\n                \"Source address checks are disabled. Setting challenge status to valid. \"\n                \"This is not recommended as this is a severe security risk!\"\n            )\n            challenge_check = True\n            invalid = False\n            error_message = None\n\n        if invalid:\n            self.state_manager.transition_to_invalid(\n                challenge_name, self.source_address, validation_error=error_message\n            )\n        elif challenge_check:\n            self.state_manager.transition_to_valid(\n                challenge_name, self.source_address, uts_now()\n            )\n        self.logger.debug(\n            \"Challenge._handle_validation_disabled() ended with: %s\", challenge_check\n        )\n        return challenge_check\n\n    def _load_address_check_configuration(self, config_dic: Dict[str, str]):\n        \"\"\"Load address check configuration.\"\"\"\n        self.logger.debug(\"Challenge._load_address_check_configuration()\")\n\n        self.config.validation_disabled = config_dic.getboolean(\n            \"Challenge\", \"challenge_validation_disable\", fallback=False\n        )\n        if self.config.validation_disabled:\n            self.logger.info(\"Challenge validation is globally disabled.\")\n        if config_dic.getboolean(\"Challenge\", \"source_address_check\", fallback=False):\n            self.logger.warning(\n                \"source_address_check is deprecated, please use forward_address_check instead\"\n            )\n            self.config.forward_address_check = True\n        else:\n            self.config.forward_address_check = config_dic.getboolean(\n                \"Challenge\", \"forward_address_check\", fallback=False\n            )\n        self.config.reverse_address_check = config_dic.getboolean(\n            \"Challenge\", \"reverse_address_check\", fallback=False\n        )\n        self.logger.debug(\"Challenge._load_address_check_configuration() ended\")\n\n    def _load_dns_configuration(self, config_dic: Dict[str, str]):\n        \"\"\"load dns config\"\"\"\n        self.logger.debug(\"Challenge._load_dns_configuration()\")\n\n        if \"Challenge\" in config_dic and \"dns_server_list\" in config_dic[\"Challenge\"]:\n            try:\n                self.config.dns_server_list = json.loads(\n                    config_dic[\"Challenge\"][\"dns_server_list\"]\n                )\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load dns_server_list from configuration: %s\",\n                    err_,\n                )\n        if (\n            \"Challenge\" in config_dic\n            and \"dns_validation_pause_timer\" in config_dic[\"Challenge\"]\n        ):\n            try:\n                self.config.dns_validation_pause_timer = int(\n                    config_dic[\"Challenge\"][\"dns_validation_pause_timer\"]\n                )\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse dns_validation_pause_timer from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"Challenge._load_dns_configuration() ended\")\n\n    def _load_proxy_configuration(self, config_dic: Dict[str, str]):\n        \"\"\"load proxy config\"\"\"\n        self.logger.debug(\"Challenge._load_proxy_configuration()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                self.proxy_server_list = json.loads(\n                    config_dic[\"DEFAULT\"][\"proxy_server_list\"]\n                )\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load proxy_server_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"Challenge._load_proxy_configuration() ended\")\n\n    def _load_configuration(self):\n        \"\"\"Load configuration from file.\"\"\"\n        self.logger.debug(\"Challenge._load_configuration()\")\n\n        config_dic = load_config(self.logger, \"Challenge\")\n        if config_dic:\n\n            try:\n                self.config.validation_timeout = int(\n                    config_dic.get(\n                        \"Challenge\",\n                        \"challenge_validation_timeout\",\n                        fallback=self.config.validation_timeout,\n                    )\n                )\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse challenge_validation_timeout from configuration: %s\",\n                    err_,\n                )\n\n            self._load_dns_configuration(config_dic)\n            self._load_proxy_configuration(config_dic)\n            self._load_address_check_configuration(config_dic)\n            self.config.async_mode = config_async_mode_load(\n                self.logger, config_dic, self.dbstore.type\n            )\n\n            self.config.sectigo_sim = config_dic.getboolean(\n                \"Challenge\", \"sectigo_sim\", fallback=False\n            )\n            self.config.tnauthlist_support = config_dic.getboolean(\n                \"Order\", \"tnauthlist_support\", fallback=False\n            )\n            self.config.email_identifier_support = config_dic.getboolean(\n                \"Order\", \"email_identifier_support\", fallback=False\n            )\n            if self.config.email_identifier_support:\n                if \"DEFAULT\" in config_dic and \"email_address\" in config_dic[\"DEFAULT\"]:\n                    self.config.email_address = config_dic[\"DEFAULT\"].get(\n                        \"email_address\"\n                    )\n                else:\n                    self.logger.warning(\n                        \"Email identifier support is enabled but no email address is configured. Disabling email identifier support.\"\n                    )\n                    self.config.email_identifier_support = False\n\n            if \"Directory\" in config_dic and \"url_prefix\" in config_dic[\"Directory\"]:\n                self.path_dic = {\n                    k: config_dic[\"Directory\"][\"url_prefix\"] + v\n                    for k, v in self.path_dic.items()\n                }\n\n            # load profiling\n            (\n                self.config.eab_profiling,\n                self.config.eab_handler,\n            ) = config_eab_profile_load(self.logger, config_dic)\n            # create validator registry\n            self.validator_registry = create_challenge_validator_registry(\n                self.logger, self.config\n            )\n\n            # Initialize factory and service after configuration is loaded\n            self._initialize_business_logic_components()\n\n        self.logger.debug(\"Challenge._load_configuration() ended\")\n\n    def _initialize_business_logic_components(self):\n        \"\"\"Initialize factory and service components that depend on configuration.\"\"\"\n        self.logger.debug(\"Challenge._initialize_business_logic_components()\")\n\n        # class creating and managing the different challenges\n        self.factory = ChallengeFactory(\n            self.repository,\n            self.logger,\n            self.server_name,\n            self.path_dic[\"chall_path\"],\n            self.config.email_address,\n        )\n        self.service = ChallengeService(\n            self.repository, self.state_manager, self.factory, self.logger\n        )\n\n        self.logger.debug(\"Challenge._initialize_business_logic_components() ended\")\n\n    def _ensure_components_initialized(self):\n        \"\"\"Ensure factory and service components are initialized.\"\"\"\n        if self.factory is None or self.service is None:\n            raise RuntimeError(\n                \"Challenge components not initialized. Call _load_configuration() first or use as context manager.\"\n            )\n\n    def _get_eab_kid_from_challenge(self, challenge_name: str) -> Optional[str]:\n        \"\"\"Extract EAB kid from challenge information.\"\"\"\n        self.logger.debug(\"Challenge._get_eab_kid_from_challenge(%s)\", challenge_name)\n\n        try:\n            challenge_dic = self.repository.get_challengeinfo_by_challengename(\n                challenge_name,\n                vlist=(\n                    \"name\",\n                    \"status__name\",\n                    \"authorization__order__account__name\",\n                    \"authorization__order__account__eab_kid\",\n                ),\n            )\n\n            eab_kid = challenge_dic.get(\"authorization__order__account__eab_kid\")\n            if eab_kid:\n                self.logger.debug(\n                    \"Challenge._get_eab_kid_from_challenge(): found eab kid: %s\",\n                    eab_kid,\n                )\n                return eab_kid\n\n            return None\n\n        except Exception as err:\n            self.logger.error(\n                \"Failed to get EAB kid from challenge %s: %s\", challenge_name, err\n            )\n            return None\n\n    def _get_challenge_profile_settings(\n        self, profile_dic: Dict[str, Any], eab_kid: str\n    ) -> Dict[str, bool]:\n        \"\"\"Extract challenge-related settings from EAB profile.\"\"\"\n        self.logger.debug(\n            \"Challenge._get_challenge_profile_settings() for kid: %s\", eab_kid\n        )\n\n        if eab_kid not in profile_dic:\n            return {}\n\n        challenge_profile = profile_dic.get(eab_kid, {}).get(\"challenge\", {})\n\n        settings = {\n            \"challenge_validation_disable\": challenge_profile.get(\n                \"challenge_validation_disable\", False\n            ),\n            \"forward_address_check\": challenge_profile.get(\n                \"forward_address_check\", False\n            ),\n            \"reverse_address_check\": challenge_profile.get(\n                \"reverse_address_check\", False\n            ),\n        }\n\n        self.logger.debug(\n            \"Challenge._get_challenge_profile_settings(): extracted settings for kid %s: %s\",\n            eab_kid,\n            settings,\n        )\n\n        self.logger.debug(\"Challenge._get_challenge_profile_settings() ended\")\n        return settings\n\n    def _apply_eab_profile_settings(self, settings: Dict[str, bool], eab_kid: str):\n        \"\"\"Apply EAB profile settings to challenge configuration.\"\"\"\n        self.logger.debug(\n            \"Challenge._apply_eab_profile_settings() for kid: %s\", eab_kid\n        )\n\n        if settings.get(\"challenge_validation_disable\"):\n            self.logger.info(\n                \"Challenge validation is disabled via EAB profiling (eab_kid: %s).\",\n                eab_kid,\n            )\n            self.config.validation_disabled = True\n\n        if settings.get(\"forward_address_check\"):\n            self.logger.info(\n                \"Forward address check is enabled via EAB profiling (eab_kid: %s).\",\n                eab_kid,\n            )\n            self.config.forward_address_check = True\n\n        if settings.get(\"reverse_address_check\"):\n            self.logger.info(\n                \"Reverse address check is enabled via EAB profiling (eab_kid: %s).\",\n                eab_kid,\n            )\n            self.config.reverse_address_check = True\n\n    def _check_challenge_validation_eabprofile(self, challenge_name: str):\n        \"\"\"Check and apply challenge validation settings from EAB profiling.\"\"\"\n        self.logger.debug(\n            \"Challenge._check_challenge_validation_eabprofile(%s)\", challenge_name\n        )\n\n        # Early return if EAB profiling is not enabled\n        if not (self.config.eab_profiling and self.config.eab_handler):\n            return\n\n        # Get EAB kid from challenge\n        eab_kid = self._get_eab_kid_from_challenge(challenge_name)\n        if not eab_kid:\n            return\n\n        # Load and apply EAB profile settings\n        try:\n            with self.config.eab_handler(self.logger) as eab_handler:\n                profile_dic = eab_handler.key_file_load()\n\n                # Check if profile contains challenge settings\n                if eab_kid in profile_dic and \"challenge\" in profile_dic[eab_kid]:\n\n                    settings = self._get_challenge_profile_settings(\n                        profile_dic, eab_kid\n                    )\n                    self._apply_eab_profile_settings(settings, eab_kid)\n\n        except Exception as err:\n            self.logger.error(\n                \"Failed to process EAB profile for challenge %s (kid: %s): %s\",\n                challenge_name,\n                eab_kid,\n                err,\n            )\n\n    def _perform_challenge_validation(\n        self, challenge_name: str, _payload: Dict[str, str]\n    ) -> bool:\n        \"\"\"Perform complete challenge validation process.\"\"\"\n        self.logger.debug(\"Challenge._perform_challenge_validation(%s)\", challenge_name)\n        try:\n            # Transition to processing state\n            self.state_manager.transition_to_processing(challenge_name)\n\n            # check if challenge validation/source address checking got configured via eab profiling\n            self._check_challenge_validation_eabprofile(challenge_name)\n            # Check if validation is disabled\n            if self.config.validation_disabled:\n                return self._handle_validation_disabled(challenge_name)\n\n            # Perform actual validation\n            validation_result = self._execute_challenge_validation(challenge_name)\n            result = self._update_challenge_state_from_validation(\n                challenge_name, validation_result\n            )\n\n        except Exception as err:\n            error_detail = self.error_handler.handle_error(\n                err, context={\"challenge_name\": challenge_name}\n            )\n            self.logger.error(\n                \"Challenge validation error for %s: %s\", challenge_name, error_detail\n            )\n\n            # Mark challenge as invalid on error\n            self.state_manager.transition_to_invalid(\n                challenge_name, self.source_address\n            )\n            self.logger.debug(\n                \"Challenge._perform_challenge_validation() ended with error\"\n            )\n            result = False\n\n        # Update challenge state based on result\n        self.logger.debug(\n            \"Challenge._perform_challenge_validation() ended with: %s\", result\n        )\n        return result\n\n    def _perform_source_address_validation(\n        self, challenge_name: str\n    ) -> Tuple[bool, bool, Optional[str]]:\n        \"\"\"Perform source address validation checks using the validator registry.\"\"\"\n        self.logger.debug(\n            \"Challenge._perform_source_address_validation(%s)\", challenge_name\n        )\n\n        # If no address checking is configured, skip validation\n        if not (self.config.forward_address_check or self.config.reverse_address_check):\n            self.logger.debug(\"Source address validation disabled\")\n            return True, False, None\n\n        challenge_info = self.repository.get_challenge_by_name(challenge_name)\n        self.logger.debug(\n            \"Challenge._perform_source_address_validation() found: %s\", challenge_info\n        )\n\n        if not challenge_info:\n            self.logger.error(\"Challenge not found: %s\", challenge_name)\n            return False, True, \"Challenge not found\"\n\n        # Create challenge context for source address validation\n        try:\n            context = ChallengeContext(\n                challenge_name=challenge_name,\n                token=challenge_info.token,\n                jwk_thumbprint=\"\",  # Not needed for source validation\n                authorization_type=\"dns\",  # Default, will be determined from challenge\n                authorization_value=challenge_info.authorization_value,\n                source_address=self.source_address,\n                dns_servers=self.config.dns_server_list,\n                timeout=self.config.validation_timeout,\n                options={\n                    \"forward_address_check\": self.config.forward_address_check,\n                    \"reverse_address_check\": self.config.reverse_address_check,\n                },\n            )\n\n            # Use the source address validator from registry\n            if self.validator_registry.is_supported(\"source-address\"):\n                result = self.validator_registry.validate_challenge(\n                    \"source-address\", context\n                )\n                if result.success:\n                    self.logger.debug(\n                        \"Source address validation passed for %s\", challenge_name\n                    )\n                    return True, False, None\n                else:\n                    self.logger.warning(\n                        \"Source address validation failed for %s: %s\",\n                        challenge_name,\n                        result.error_message,\n                    )\n                    return False, result.invalid, result.error_message\n            else:\n                self.logger.warning(\"Source address validator not available\")\n                return True, False, None  # Don't fail if validator not available\n\n        except Exception as e:\n            self.logger.error(\n                \"Source address validation error for %s: %s\", challenge_name, str(e)\n            )\n            return (\n                False,\n                True,\n                f\"Source address validation error for {challenge_name}: {str(e)}\",\n            )\n\n    def _perform_validation_with_retry(\n        self, challenge_type: str, context: ChallengeContext\n    ) -> ValidationResult:\n        \"\"\"Perform validation with retry logic for certain challenge types.\"\"\"\n\n        retry_challenge_types = [\"dns-01\", \"email-reply-00\"]\n        max_attempts = 5 if challenge_type in retry_challenge_types else 1\n\n        for attempt in range(max_attempts):\n            result = self.validator_registry.validate_challenge(challenge_type, context)\n\n            # Break if successful or definitely invalid\n            if result.success or result.invalid:\n                break\n\n            # Sleep before retry for certain challenge types\n            if challenge_type in retry_challenge_types and attempt < max_attempts - 1:\n                time.sleep(self.config.dns_validation_pause_timer)\n            else:\n                if not result.success:\n                    self.logger.error(\n                        \"No more retries left for challenge type %s. Invalidating challenge.\",\n                        challenge_type,\n                    )\n                    result.invalid = True\n        return result\n\n    def _start_async_validation(self, challenge_name: str, payload: Dict[str, str]):\n        \"\"\"Start asynchronous challenge validation.\"\"\"\n        self.logger.debug(\"Challenge._start_async_validation(%s)\", challenge_name)\n        # start challenge validation in separate thread\n        twrv = Thread(\n            target=self._perform_challenge_validation, args=(challenge_name, payload)\n        )\n        twrv.start()\n        if self.config.async_mode:\n            # full async mode - do not wait for result\n            self.logger.info(\n                \"asynchronous Challenge validation enabled, not waiting for result\"\n            )\n        else:\n            twrv.join(timeout=self.config.validation_timeout)\n\n    def _update_challenge_state_from_validation(\n        self, challenge_name: str, validation_result: ValidationResult\n    ) -> bool:\n        \"\"\"Update challenge state based on validation result.\"\"\"\n\n        if validation_result.invalid:\n            self.state_manager.transition_to_invalid(\n                challenge_name, self.source_address, validation_result.error_message\n            )\n            return False\n        elif validation_result.success:\n            self.state_manager.transition_to_valid(\n                challenge_name, self.source_address, uts_now()\n            )\n            return True\n        else:\n            # Validation inconclusive - keep in processing state\n            return False\n\n    def _validate_tnauthlist_payload(\n        self, payload: Dict[str, str], challenge_info: ChallengeInfo\n    ) -> Dict[str, str]:\n        \"\"\"Validate tnauthlist payload.\"\"\"\n        self.logger.debug(\"Challenge._validate_tnauthlist_payload()\")\n        if challenge_info.type == \"tkauth-01\":\n            if \"atc\" not in payload:\n                self.logger.error(\n                    \"TNauthlist payload validation failed. atc claim is missing\"\n                )\n                return self._create_error_response(\n                    400, self.err_msg_dic[\"malformed\"], \"atc claim is missing\"\n                )\n            if not payload[\"atc\"]:\n                self.logger.error(\n                    \"TNauthlist payload validation failed. SPC token is missing\"\n                )\n                return self._create_error_response(\n                    400, self.err_msg_dic[\"malformed\"], \"SPC token is missing\"\n                )\n\n        return {\"code\": 200}\n\n    # === Public Implementation Methods ===\n\n    def get_challenge_details(self, url: str) -> Dict[str, str]:\n        \"\"\"Get challenge details from URL (replaces get).\"\"\"\n        challenge_name = self._extract_challenge_name_from_url(url)\n        self.logger.debug(\"Challenge.get_challenge_details(%s)\", challenge_name)\n\n        try:\n            challenge_info = self.repository.get_challenge_by_name(challenge_name)\n            if not challenge_info:\n                return {\"code\": 404, \"data\": {}}\n\n            return {\n                \"code\": 200,\n                \"data\": {\n                    \"type\": challenge_info.type,\n                    \"status\": challenge_info.status,\n                    \"token\": challenge_info.token,\n                    \"validated\": challenge_info.validated,\n                },\n            }\n        except Exception as err:\n            error_detail = self.error_handler.handle_error(err)\n            return self.error_handler.create_acme_error_response(error_detail, 500)\n\n    def process_challenge_request(self, content: str) -> Dict[str, str]:\n        \"\"\"Process challenge request (replaces parse).\"\"\"\n        self.logger.debug(\"Challenge.process_challenge_request()\")\n\n        self._ensure_components_initialized()\n\n        try:\n            # Check message format\n            (\n                code,\n                message,\n                detail,\n                protected,\n                payload,\n                _account_name,\n            ) = self.message.check(content)\n\n            if code != 200:\n                return self._create_error_response(code, message, detail)\n\n            if \"url\" not in protected:\n                return self._create_error_response(\n                    400,\n                    self.err_msg_dic[\"malformed\"],\n                    \"url missing in protected header\",\n                )\n\n            challenge_name = self._extract_challenge_name_from_url(protected[\"url\"])\n            if not challenge_name:\n                return self._create_error_response(\n                    400, self.err_msg_dic[\"malformed\"], \"could not get challenge\"\n                )\n\n            challenge_info = self.repository.get_challenge_by_name(challenge_name)\n            if not challenge_info:\n                return self._create_error_response(\n                    400,\n                    self.err_msg_dic[\"malformed\"],\n                    f\"invalid challenge: {challenge_name}\",\n                )\n\n            return self._handle_challenge_validation_request(\n                code, payload, protected, challenge_name, challenge_info\n            )\n\n        except Exception as err:\n            error_detail = self.error_handler.handle_error(err)\n            return self.error_handler.create_acme_error_response(error_detail, 500)\n\n    def retrieve_challenge_set(\n        self,\n        authz_name: str,\n        _auth_status: str,\n        token: str,\n        _tnauth: bool,\n        id_type: str = \"dns\",\n        id_value: str = None,\n    ) -> List[Dict[str, str]]:\n        \"\"\"Retrieve existing or create new challenge set (replaces challengeset_get).\"\"\"\n        self.logger.debug(\n            \"Challenge.retrieve_challenge_set() for auth: %s:%s\", authz_name, id_value\n        )\n\n        self._ensure_components_initialized()\n\n        result = []\n        try:\n            result = self.service.get_challenge_set_for_authorization(\n                authorization_name=authz_name,\n                token=token,\n                id_type=id_type,\n                id_value=id_value,\n                config=self.config,\n                url=f\"{self.server_name}{self.path_dic['chall_path']}\",\n            )\n        except Exception as err:\n            error_detail = self.error_handler.handle_error(err)\n            self.logger.error(\n                \"Failed to retrieve challenge set: %s\", error_detail.message\n            )\n\n        self.logger.debug(\n            \"Challenge.retrieve_challenge_set() ended with %d challenges\", len(result)\n        )\n\n        return result\n\n    # === Legacy API Compatibility ===\n\n    def get(self, url: str) -> Dict[str, str]:\n        \"\"\"Legacy API compatibility - use get_challenge_details instead.\"\"\"\n        self.logger.debug(\"Challenge.get() called - legacy API\")\n        return self.get_challenge_details(url)\n\n    def challengeset_get(self, *args, **kwargs) -> List[Dict[str, str]]:\n        \"\"\"Legacy API compatibility - use retrieve_challenge_set instead.\"\"\"\n        self.logger.debug(\"Challenge.challengeset_get() called - legacy API\")\n        return self.retrieve_challenge_set(*args, **kwargs)\n\n    def parse(self, content: str) -> Dict[str, str]:\n        \"\"\"Legacy API compatibility - use process_challenge_request instead.\"\"\"\n        self.logger.debug(\"Challenge.parse() called - legacy API\")\n        return self.process_challenge_request(content)\n"
  },
  {
    "path": "acme_srv/challenge_business_logic.py",
    "content": "\"\"\"\nSeparation of challenge validation logic and database/state management\noperations for challenge processing.\n\n\"\"\"\nfrom typing import Dict, List, Optional, Any\nfrom dataclasses import dataclass\nimport logging\nimport json\nfrom abc import ABC, abstractmethod\n\n\n@dataclass\nclass ChallengeInfo:\n    \"\"\"Information about a challenge.\"\"\"\n\n    name: str\n    type: str\n    token: str\n    status: str\n    authorization_name: str\n    authorization_type: str\n    authorization_value: str\n    url: str\n    validated: Optional[str] = None\n    validation_error: Optional[str] = None\n\n\n@dataclass\nclass ChallengeCreationRequest:\n    \"\"\"Request for creating a new challenge.\"\"\"\n\n    authorization_name: str\n    challenge_type: str\n    token: str\n    value: Optional[str] = None\n    expiry: int = 3600\n\n\n@dataclass\nclass ChallengeUpdateRequest:\n    \"\"\"Request for updating a challenge.\"\"\"\n\n    name: str\n    status: Optional[str] = None\n    source: Optional[str] = None\n    validated: Optional[int] = None\n    keyauthorization: Optional[str] = None\n    validation_error: Optional[str] = None\n\n\nclass ChallengeRepository(ABC):\n    \"\"\"Abstract repository for challenge data operations.\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    @abstractmethod\n    def find_challenges_by_authorization(\n        self, authorization_name: str\n    ) -> List[ChallengeInfo]:\n        \"\"\"Find all challenges for a given authorization.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def get_challenge_by_name(self, name: str) -> Optional[ChallengeInfo]:\n        \"\"\"Get challenge information by name.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def get_challengeinfo_by_challengename(self, name: str) -> Optional[ChallengeInfo]:\n        \"\"\"Get challenge information by challenge name.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def create_challenge(self, request: ChallengeCreationRequest) -> Optional[str]:\n        \"\"\"Create a new challenge and return its name.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def update_challenge(self, request: ChallengeUpdateRequest) -> bool:\n        \"\"\"Update an existing challenge.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def update_authorization_status(self, challenge_name: str, status: str) -> bool:\n        \"\"\"Update authorization status based on challenge.\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def get_account_jwk(self, challenge_name: str) -> Optional[Dict[str, Any]]:\n        \"\"\"Get JWK for the account associated with the challenge.\"\"\"\n        pass  # pragma: no cover\n\n\nclass ChallengeStateManager:\n    \"\"\"Manages challenge state transitions and business rules.\"\"\"\n\n    def __init__(self, repository: ChallengeRepository, logger: logging.Logger):\n        self.repository = repository\n        self.logger = logger\n\n    def transition_to_processing(self, challenge_name: str) -> bool:\n        \"\"\"Transition challenge to processing state.\"\"\"\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_processing(%s)\", challenge_name\n        )\n\n        update_request = ChallengeUpdateRequest(\n            name=challenge_name, status=\"processing\"\n        )\n        result = self.repository.update_challenge(update_request)\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_processing() updated challenge %s to processing/%s\",\n            challenge_name,\n            result,\n        )\n        return result\n\n    def transition_to_valid(\n        self,\n        challenge_name: str,\n        source_address: Optional[str] = None,\n        validated_timestamp: Optional[int] = None,\n    ) -> bool:\n        \"\"\"Transition challenge to valid state.\"\"\"\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_valid(%s)\", challenge_name\n        )\n\n        update_request = ChallengeUpdateRequest(\n            name=challenge_name,\n            status=\"valid\",\n            source=source_address,\n            validated=validated_timestamp,\n        )\n\n        success = self.repository.update_challenge(update_request)\n        if success:\n            success = self.repository.update_authorization_status(\n                challenge_name, \"valid\"\n            )\n\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_valid() updated challenge %s to valid/%s\",\n            challenge_name,\n            success,\n        )\n        return success\n\n    def transition_to_invalid(\n        self,\n        challenge_name: str,\n        source_address: Optional[str] = None,\n        validation_error: Optional[str] = None,\n    ) -> bool:\n        \"\"\"Transition challenge to invalid state.\"\"\"\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_invalid(%s)\", challenge_name\n        )\n\n        update_request = ChallengeUpdateRequest(\n            name=challenge_name,\n            status=\"invalid\",\n            source=source_address,\n            validation_error=validation_error,\n        )\n\n        success = self.repository.update_challenge(update_request)\n        if success:\n            success = self.repository.update_authorization_status(\n                challenge_name, \"invalid\"\n            )\n\n        self.logger.debug(\n            \"ChallengeStateManager.transition_to_invalid() ended: updated challenge %s to invalid/%s\",\n            challenge_name,\n            success,\n        )\n        return success\n\n    def update_key_authorization(\n        self, challenge_name: str, key_authorization: str\n    ) -> bool:\n        \"\"\"Update challenge with key authorization.\"\"\"\n        self.logger.debug(\n            \"ChallengeStateManager.update_key_authorization(%s)\", challenge_name\n        )\n\n        update_request = ChallengeUpdateRequest(\n            name=challenge_name, keyauthorization=key_authorization\n        )\n        self.logger.debug(\n            \"ChallengeStateManager.update_key_authorization() ended: updating challenge %s with key authorization\",\n            challenge_name,\n        )\n        return self.repository.update_challenge(update_request)\n\n\nclass ChallengeFactory:\n    \"\"\"Factory for creating different types of challenges.\"\"\"\n\n    def __init__(\n        self,\n        repository: ChallengeRepository,\n        logger: logging.Logger,\n        server_name: str,\n        challenge_path: str,\n        email_address: Optional[str] = None,\n    ):\n        self.repository = repository\n        self.logger = logger\n        self.server_name = server_name\n        self.challenge_path = challenge_path\n        self.email_address = email_address\n\n    def create_standard_challenge_set(\n        self, authorization_name: str, token: str, id_type: str, value: str\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Create standard ACME challenge set (http-01, dns-01, tls-alpn-01).\"\"\"\n        self.logger.debug(\n            \"ChallengeFactory.create_standard_challenge_set(%s)\", authorization_name\n        )\n\n        challenge_types = [\"http-01\", \"dns-01\", \"tls-alpn-01\"]\n        # Skip DNS challenge for IP identifiers\n        if id_type == \"ip\":\n            self.logger.debug(\n                \"ChallengeFactory.create_standard_challenge_set(): Skipping dns-01 challenge for IP identifier\"\n            )\n            challenge_types.remove(\"dns-01\")\n\n        challenges = []\n        for challenge_type in challenge_types:\n            challenge_dict = self.create_single_challenge(\n                authorization_name, challenge_type, token, value\n            )\n            if challenge_dict:\n                challenges.append(challenge_dict)\n        self.logger.debug(\n            \"ChallengeFactory.create_standard_challenge_set() ended: Created %d challenges\",\n            len(challenges),\n        )\n        return challenges\n\n    def create_email_reply_challenge(\n        self,\n        authorization_name: str,\n        token: str,\n        email_address: str,\n        sender_address: str,\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"Create email-reply-00 challenge.\"\"\"\n        self.logger.debug(\n            \"ChallengeFactory.create_email_reply_challenge(%s)\", email_address\n        )\n        if sender_address:\n            self.email_address = sender_address\n\n        result = self.create_single_challenge(\n            authorization_name, \"email-reply-00\", token, email_address\n        )\n        self.logger.debug(\n            \"ChallengeFactory.create_email_reply_challenge() ended: %s\", result\n        )\n        return result\n\n    def create_tkauth_challenge(\n        self, authorization_name: str, token: str\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"Create tkauth-01 challenge.\"\"\"\n        self.logger.debug(\n            \"ChallengeFactory.create_tkauth_challenge(%s)\", authorization_name\n        )\n\n        result = self.create_single_challenge(authorization_name, \"tkauth-01\", token)\n        self.logger.debug(\n            \"ChallengeFactory.create_tkauth_challenge() ended: %s\", result\n        )\n        return result\n\n    def create_single_challenge(\n        self,\n        authorization_name: str,\n        challenge_type: str,\n        token: str,\n        value: Optional[str] = None,\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"Create a single challenge of the specified type.\"\"\"\n        self.logger.debug(\n            \"ChallengeFactory.create_single_challenge(): Creating %s challenge for authorization: %s\",\n            challenge_type,\n            authorization_name,\n        )\n        request = ChallengeCreationRequest(\n            authorization_name=authorization_name,\n            challenge_type=challenge_type,\n            token=token,\n            value=value,\n        )\n        challenge_name = self.repository.create_challenge(request)\n        if not challenge_name:\n            self.logger.error(\"Failed to create %s challenge\", challenge_type)\n            return None\n\n        challenge_dict = {\n            \"type\": challenge_type,\n            \"url\": f\"{self.server_name}{self.challenge_path}{challenge_name}\",\n            \"token\": token,\n            \"status\": \"pending\",\n        }\n\n        # Add type-specific properties\n        if challenge_type == \"email-reply-00\" and self.email_address:\n            challenge_dict[\"from\"] = self.email_address\n            result = self.repository.get_challengeinfo_by_challengename(\n                challenge_name,\n                vlist=(\"name\", \"keyauthorization\", \"authorization__value\"),\n            )\n            if (\n                result\n                and \"keyauthorization\" in result\n                and \"authorization__value\" in result\n            ):\n                # send challange email\n                # pylint: disable=import-outside-toplevel\n                from acme_srv.email_handler import EmailHandler\n\n                with EmailHandler(logger=self.logger) as email_handler:\n                    email_handler.send_email_challenge(\n                        to_address=result[\"authorization__value\"],\n                        token1=result[\"keyauthorization\"],\n                    )\n\n        elif challenge_type == \"tkauth-01\":\n            challenge_dict[\"tkauth-type\"] = \"atc\"\n        elif challenge_type == \"sectigo-email-01\":\n            challenge_dict[\"status\"] = \"valid\"\n            challenge_dict.pop(\"token\", None)\n\n        self.logger.debug(\n            \"ChallengeFactory.create_single_challenge() ended: created challenge %s/%s\",\n            challenge_type,\n            challenge_name,\n        )\n        return challenge_dict\n\n\nclass ChallengeService:\n    \"\"\"High-level service for challenge operations.\"\"\"\n\n    def __init__(\n        self,\n        repository: ChallengeRepository,\n        state_manager: ChallengeStateManager,\n        factory: ChallengeFactory,\n        logger: logging.Logger,\n    ):\n        self.repository = repository\n        self.state_manager = state_manager\n        self.factory = factory\n        self.logger = logger\n\n    def get_challenge_set_for_authorization(\n        self,\n        authorization_name: str,\n        token: str,\n        id_type: str,\n        id_value: str,\n        config: Dict[str, Any],\n        url: str = \"\",\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Get challenge set for an authorization.\"\"\"\n        self.logger.debug(\n            \"ChallengeService.get_challenge_set_for_authorization(%s)\",\n            authorization_name,\n        )\n\n        # Check for existing challenges\n        existing_challenges = self.repository.find_challenges_by_authorization(\n            authorization_name\n        )\n\n        if existing_challenges:\n            self.logger.debug(\n                \"ChallengeService.get_challenge_set_for_authorization(): Found existing challenges\"\n            )\n            return self._format_existing_challenges(\n                challenges=existing_challenges, url=url, config=config\n            )\n\n        # Create new challenge set\n        self.logger.debug(\n            \"ChallengeService.get_challenge_set_for_authorization(%s): Creating new challenge set\",\n            authorization_name,\n        )\n\n        return self._create_new_challenge_set(\n            authorization_name,\n            token,\n            id_type,\n            id_value,\n            config,\n        )\n\n    def _format_existing_challenges(\n        self,\n        challenges: List[ChallengeInfo],\n        url: str = \"\",\n        config: Dict[str, Any] = None,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Format existing challenges for response.\"\"\"\n        self.logger.debug(\n            \"ChallengeService._format_existing_challenges(%s)\", len(challenges)\n        )\n        challenge_list = []\n        for challenge in challenges:\n            challenge_dict = {\n                \"type\": challenge.type,\n                \"url\": f\"{url}{challenge.name}\",\n                \"token\": challenge.token,\n                \"status\": challenge.status,\n            }\n\n            if challenge.validation_error:\n                # add error message if present\n                try:\n                    challenge_dict[\"error\"] = json.loads(challenge.validation_error)\n                except Exception:\n                    challenge_dict[\"error\"] = {\n                        \"status\": 400,\n                        \"type\": \"urn:ietf:params:acme:error:unknown\",\n                        \"detail\": challenge.validation_error,\n                    }\n\n            if challenge.type == \"email-reply-00\" and config.email_address:\n                challenge_dict[\"from\"] = config.email_address\n\n            challenge_list.append(challenge_dict)\n\n        self.logger.debug(\n            \"ChallengeService._format_existing_challenges() ended with %s challenges\",\n            len(challenge_list),\n        )\n        return challenge_list\n\n    def _create_new_challenge_set(\n        self,\n        authorization_name: str,\n        token: str,\n        id_type: str,\n        id_value: str,\n        config: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Create a new challenge set based on configuration.\"\"\"\n        self.logger.debug(\n            \"ChallengeService._create_new_challenge_set(%s)\", authorization_name\n        )\n\n        challenge_list = []\n\n        if config.email_identifier_support and config.email_address and \"@\" in id_value:\n            # in case of an email identifier we return only one challenge\n            self.logger.debug(\n                \"ChallengeService._create_new_challenge_set(): Creating email-reply-00 challenge for email identifier\"\n            )\n            challenge = self.factory.create_email_reply_challenge(\n                authorization_name, token, id_value, config.email_address\n            )\n            return [challenge] if challenge else []\n\n        if config.tnauthlist_support and id_type.lower() == \"tnauthlist\":\n            # in case of an tnauthlist identifier we return only one challenge\n            self.logger.debug(\n                \"ChallengeService._create_new_challenge_set(): Creating tkauth-01 challenge for tnauthlist identifier\"\n            )\n            challenge = self.factory.create_tkauth_challenge(authorization_name, token)\n            return [challenge] if challenge else []\n\n        if config.sectigo_sim:\n\n            challenge = self.factory.create_single_challenge(\n                authorization_name, \"sectigo-email-01\", token\n            )\n            challenge_list.append(challenge) if challenge else None\n\n        challenge_list.extend(\n            self.factory.create_standard_challenge_set(\n                authorization_name, token, id_type, id_value\n            )\n        )\n\n        self.logger.debug(\n            \"ChallengeService._create_new_challenge_set() ended with %s challenges\",\n            len(challenge_list),\n        )\n        return challenge_list\n"
  },
  {
    "path": "acme_srv/challenge_error_handling.py",
    "content": "\"\"\"\nEnhanced error handling system for challenge processing.\n\nThis module provides a comprehensive error handling framework with custom exceptions,\nerror categorization, and standardized error responses for challenge operations.\n\"\"\"\nfrom typing import Dict, Optional, Any, List\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport traceback\nimport logging\n\n\nclass ErrorCategory(Enum):\n    \"\"\"Categories of errors that can occur during challenge processing.\"\"\"\n\n    VALIDATION_ERROR = \"validation_error\"\n    NETWORK_ERROR = \"network_error\"\n    DATABASE_ERROR = \"database_error\"\n    CONFIGURATION_ERROR = \"configuration_error\"\n    AUTHENTICATION_ERROR = \"authentication_error\"\n    MALFORMED_REQUEST = \"malformed_request\"\n    TIMEOUT_ERROR = \"timeout_error\"\n    UNKNOWN_ERROR = \"unknown_error\"\n\n\nclass ErrorSeverity(Enum):\n    \"\"\"Severity levels for errors.\"\"\"\n\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\n\n@dataclass\nclass ErrorDetail:\n    \"\"\"Detailed error information.\"\"\"\n\n    category: ErrorCategory\n    severity: ErrorSeverity\n    message: str\n    details: Optional[Dict[str, Any]] = None\n    suggestion: Optional[str] = None\n    error_code: Optional[str] = None\n\n\nclass ChallengeError(Exception):\n    \"\"\"Base exception for all challenge-related errors.\"\"\"\n\n    def __init__(\n        self,\n        message: str,\n        category: ErrorCategory = ErrorCategory.UNKNOWN_ERROR,\n        severity: ErrorSeverity = ErrorSeverity.MEDIUM,\n        details: Optional[Dict[str, Any]] = None,\n        suggestion: Optional[str] = None,\n        error_code: Optional[str] = None,\n    ):\n        super().__init__(message)\n        self.error_detail = ErrorDetail(\n            category=category,\n            severity=severity,\n            message=message,\n            details=details or {},\n            suggestion=suggestion,\n            error_code=error_code,\n        )\n\n\nclass ValidationError(ChallengeError):\n    \"\"\"Raised when challenge validation fails.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(message, category=ErrorCategory.VALIDATION_ERROR, **kwargs)\n\n\nclass NetworkError(ChallengeError):\n    \"\"\"Raised when network operations fail.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(message, category=ErrorCategory.NETWORK_ERROR, **kwargs)\n\n\nclass DatabaseError(ChallengeError):\n    \"\"\"Raised when database operations fail.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(\n            message,\n            category=ErrorCategory.DATABASE_ERROR,\n            severity=ErrorSeverity.HIGH,\n            **kwargs,\n        )\n\n\nclass ConfigurationError(ChallengeError):\n    \"\"\"Raised when configuration is invalid.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(\n            message,\n            category=ErrorCategory.CONFIGURATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            **kwargs,\n        )\n\n\nclass AuthenticationError(ChallengeError):\n    \"\"\"Raised when authentication fails.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(\n            message,\n            category=ErrorCategory.AUTHENTICATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            **kwargs,\n        )\n\n\nclass MalformedRequestError(ChallengeError):\n    \"\"\"Raised when request is malformed.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(message, category=ErrorCategory.MALFORMED_REQUEST, **kwargs)\n\n\nclass TimeoutError(ChallengeError):\n    \"\"\"Raised when operations timeout.\"\"\"\n\n    def __init__(self, message: str, **kwargs):\n        super().__init__(message, category=ErrorCategory.TIMEOUT_ERROR, **kwargs)\n\n\nclass UnsupportedChallengeTypeError(ValidationError):\n    \"\"\"Raised when an unsupported challenge type is encountered.\"\"\"\n\n    def __init__(self, challenge_type: str, supported_types: List[str]):\n        message = f\"Unsupported challenge type: {challenge_type}\"\n        super().__init__(\n            message,\n            details={\n                \"challenge_type\": challenge_type,\n                \"supported_types\": supported_types,\n            },\n            suggestion=f\"Use one of the supported types: {', '.join(supported_types)}\",\n            error_code=\"UNSUPPORTED_CHALLENGE_TYPE\",\n        )\n\n\nclass DNSResolutionError(NetworkError):\n    \"\"\"Raised when DNS resolution fails.\"\"\"\n\n    def __init__(self, domain: str, dns_servers: Optional[List[str]] = None):\n        message = f\"DNS resolution failed for domain: {domain}\"\n        super().__init__(\n            message,\n            details={\"domain\": domain, \"dns_servers\": dns_servers},\n            suggestion=\"Check domain validity and DNS server configuration\",\n            error_code=\"DNS_RESOLUTION_FAILED\",\n        )\n\n\nclass HTTPChallengeError(ValidationError):\n    \"\"\"Raised when HTTP challenge validation fails.\"\"\"\n\n    def __init__(self, url: str, expected: str, received: str):\n        message = f\"HTTP challenge validation failed for {url}\"\n        super().__init__(\n            message,\n            details={\n                \"url\": url,\n                \"expected_response\": expected,\n                \"received_response\": received,\n            },\n            suggestion=\"Ensure the challenge file is accessible and contains the correct token\",\n            error_code=\"HTTP_CHALLENGE_FAILED\",\n        )\n\n\nclass DNSChallengeError(ValidationError):\n    \"\"\"Raised when DNS challenge validation fails.\"\"\"\n\n    def __init__(self, dns_record: str, expected_hash: str, found_records: List[str]):\n        message = f\"DNS challenge validation failed for {dns_record}\"\n        super().__init__(\n            message,\n            details={\n                \"dns_record\": dns_record,\n                \"expected_hash\": expected_hash,\n                \"found_records\": found_records,\n            },\n            suggestion=\"Ensure the DNS TXT record is properly configured\",\n            error_code=\"DNS_CHALLENGE_FAILED\",\n        )\n\n\nclass TLSALPNChallengeError(ValidationError):\n    \"\"\"Raised when TLS-ALPN challenge validation fails.\"\"\"\n\n    def __init__(self, domain: str, expected_extension: str):\n        message = f\"TLS-ALPN challenge validation failed for {domain}\"\n        super().__init__(\n            message,\n            details={\"domain\": domain, \"expected_extension\": expected_extension},\n            suggestion=\"Ensure the TLS certificate contains the required extension\",\n            error_code=\"TLS_ALPN_CHALLENGE_FAILED\",\n        )\n\n\nclass ErrorHandler:\n    \"\"\"Centralized error handling and logging.\"\"\"\n\n    def __init__(self, logger: logging.Logger):\n        self.logger = logger\n        self.error_counts: Dict[ErrorCategory, int] = {}\n\n    def handle_error(\n        self, error: Exception, context: Optional[Dict[str, Any]] = None\n    ) -> ErrorDetail:\n        \"\"\"Handle and log an error, returning structured error information.\"\"\"\n        self.logger.debug(\"ErrorHandler.handle_error(): %s\", str(error))\n        if isinstance(error, ChallengeError):\n            error_detail = error.error_detail\n        else:\n            # Convert generic exceptions to ChallengeError\n            error_detail = ErrorDetail(\n                category=ErrorCategory.UNKNOWN_ERROR,\n                severity=ErrorSeverity.MEDIUM,\n                message=str(error),\n                details={\"exception_type\": type(error).__name__},\n            )\n\n        # Add context information\n        if context:\n            error_detail.details.update(context)\n\n        # Log the error\n        self._log_error(error_detail, error)\n\n        # Update error counts\n        # self._update_error_counts(error_detail.category)\n\n        return error_detail\n\n    def _log_error(self, error_detail: ErrorDetail, original_error: Exception):\n        \"\"\"Log error with appropriate level based on severity.\"\"\"\n        self.logger.debug(\"ErrorHandler._log_error(): %s\", str(original_error))\n        log_message = f\"[{error_detail.category.value}] {error_detail.message}\"\n\n        if error_detail.details:\n            log_message += f\" | Details: {error_detail.details}\"\n\n        if error_detail.severity == ErrorSeverity.CRITICAL:\n            self.logger.critical(log_message, exc_info=True)\n        elif error_detail.severity == ErrorSeverity.HIGH:\n            self.logger.error(log_message)\n        elif error_detail.severity == ErrorSeverity.MEDIUM:\n            self.logger.warning(log_message)\n        else:\n            self.logger.info(log_message)\n\n        # Log stack trace for debugging in debug mode\n        if self.logger.isEnabledFor(logging.DEBUG):\n            self.logger.debug(\n                \"Stack trace for error: %s\",\n                \"\".join(\n                    traceback.format_exception(\n                        type(original_error),\n                        original_error,\n                        original_error.__traceback__,\n                    )\n                ),\n            )\n\n    def create_acme_error_response(\n        self, error_detail: ErrorDetail, status_code: int = 400\n    ) -> Dict[str, Any]:\n        \"\"\"Create an ACME-compliant error response.\"\"\"\n\n        # Map internal categories to ACME error types\n        acme_error_type_map = {\n            ErrorCategory.VALIDATION_ERROR: \"incorrectResponse\",\n            ErrorCategory.NETWORK_ERROR: \"connection\",\n            ErrorCategory.MALFORMED_REQUEST: \"malformed\",\n            ErrorCategory.AUTHENTICATION_ERROR: \"unauthorized\",\n            ErrorCategory.TIMEOUT_ERROR: \"connection\",\n            ErrorCategory.CONFIGURATION_ERROR: \"serverInternal\",\n            ErrorCategory.DATABASE_ERROR: \"serverInternal\",\n            ErrorCategory.UNKNOWN_ERROR: \"serverInternal\",\n        }\n\n        acme_type = acme_error_type_map.get(error_detail.category, \"serverInternal\")\n\n        response = {\n            \"code\": status_code,\n            \"type\": f\"urn:ietf:params:acme:error:{acme_type}\",\n            \"detail\": error_detail.message,\n        }\n\n        # Add additional context if available\n        if error_detail.suggestion:\n            response[\"detail\"] += f\" Suggestion: {error_detail.suggestion}\"\n\n        return response\n\n\nclass ErrorRecovery:\n    \"\"\"Provides error recovery strategies.\"\"\"\n\n    def __init__(self, logger: logging.Logger):\n        self.logger = logger\n\n    def should_retry(self, error_detail: ErrorDetail, attempt_count: int) -> bool:\n        \"\"\"Determine if an operation should be retried based on error type.\"\"\"\n\n        # Don't retry beyond maximum attempts\n        if attempt_count >= 3:\n            return False\n\n        # Retry network errors and timeouts\n        if error_detail.category in [\n            ErrorCategory.NETWORK_ERROR,\n            ErrorCategory.TIMEOUT_ERROR,\n        ]:\n            return True\n\n        # Don't retry validation errors, malformed requests, or authentication errors\n        if error_detail.category in [\n            ErrorCategory.VALIDATION_ERROR,\n            ErrorCategory.MALFORMED_REQUEST,\n            ErrorCategory.AUTHENTICATION_ERROR,\n        ]:\n            return False\n\n        # Retry database errors (might be temporary)\n        if error_detail.category == ErrorCategory.DATABASE_ERROR:\n            return True\n\n        return False\n\n    def get_retry_delay(self, attempt_count: int) -> float:\n        \"\"\"Get delay before retry with exponential backoff.\"\"\"\n        return min(2**attempt_count, 30)  # Max 30 seconds\n"
  },
  {
    "path": "acme_srv/challenge_registry_setup.py",
    "content": "\"\"\"\nRegistry setup utilities for creating and configuring challenge validator registries.\n\nThis module provides factory functions for creating pre-configured challenge\nvalidator registries with all standard ACME challenge types.\n\"\"\"\nfrom typing import Dict, Any, Optional\nimport logging\nfrom .challenge_validators import (\n    ChallengeValidatorRegistry,\n    HttpChallengeValidator,\n    DnsChallengeValidator,\n    TlsAlpnChallengeValidator,\n    EmailReplyChallengeValidator,\n    TkauthChallengeValidator,\n    SourceAddressValidator,\n)\n\n\ndef create_challenge_validator_registry(\n    logger: logging.Logger, config: Optional[Dict[str, Any]] = None\n) -> ChallengeValidatorRegistry:\n    \"\"\"Create a fully configured challenge validator registry with all standard validators\"\"\"\n\n    logger.debug(\"challenge_registry_setup.create_challenge_validator_registry()\")\n    registry = ChallengeValidatorRegistry(logger)\n\n    # Register standard ACME challenge validators\n    registry.register_validator(HttpChallengeValidator(logger))\n    registry.register_validator(DnsChallengeValidator(logger))\n    registry.register_validator(TlsAlpnChallengeValidator(logger))\n\n    if config.email_identifier_support:\n        # Register Email-Reply challenge validator if configured\n        registry.register_validator(EmailReplyChallengeValidator(logger))\n    if config.tnauthlist_support:\n        # Register Tkauth challenge validator if configured\n        registry.register_validator(TkauthChallengeValidator(logger))\n\n    # Register Source Address validator if address checking is enabled\n    # if config.forward_address_check or config.reverse_address_check:\n    registry.register_validator(\n        SourceAddressValidator(\n            logger,\n            forward_check=config.forward_address_check,\n            reverse_check=config.reverse_address_check,\n        )\n    )\n\n    logger.debug(\n        \"create_challenge_validator_registry(): Registry created with %d validators: %s\",\n        len(registry.get_supported_types()),\n        \", \".join(registry.get_supported_types()),\n    )\n\n    logger.debug(\"challenge_registry_setup.create_challenge_validator_registry() ended\")\n    return registry\n\n\ndef create_custom_registry(\n    logger: logging.Logger,\n    validator_classes: list,\n    _config: Optional[Dict[str, Any]] = None,\n) -> ChallengeValidatorRegistry:\n    \"\"\"\n    Create a custom challenge validator registry with specified validators.\n\n    Args:\n        logger: Logger instance for validation operations\n        validator_classes: List of validator classes to register\n        config: Optional configuration dictionary for validator setup\n\n    Returns:\n        ChallengeValidatorRegistry: Configured registry with specified validators\n    \"\"\"\n    registry = ChallengeValidatorRegistry(logger)\n\n    for validator_class in validator_classes:\n        validator = validator_class(logger)\n        registry.register_validator(validator)\n\n    logger.info(\n        \"Custom challenge validator registry created with %d validators\",\n        len(registry.get_supported_types()),\n    )\n\n    return registry\n"
  },
  {
    "path": "acme_srv/challenge_validators/__init__.py",
    "content": "\"\"\"\nChallenge Validators Package.\n\nThis package provides a modular system for ACME challenge validation using\nthe Strategy pattern. Each challenge type has its own validator class with\nclear separation of concerns.\n\nUsage:\n    from challenge_validators import ChallengeValidatorRegistry\n    from challenge_validators.http_validator import HttpChallengeValidator\n\n    registry = ChallengeValidatorRegistry(logger)\n    registry.register_validator(HttpChallengeValidator(logger))\n\"\"\"\n\n# Import base classes and common structures\nfrom .base import (\n    ChallengeValidator,\n    ChallengeContext,\n    ValidationResult,\n    ChallengeValidationError,\n    ValidationTimeoutError,\n    InvalidChallengeTypeError,\n)\n\n# Import registry\nfrom .registry import ChallengeValidatorRegistry\n\n# Import all validator implementations\nfrom .http_validator import HttpChallengeValidator\nfrom .dns_validator import DnsChallengeValidator\nfrom .tls_alpn_validator import TlsAlpnChallengeValidator\nfrom .email_reply_validator import EmailReplyChallengeValidator\nfrom .tkauth_validator import TkauthChallengeValidator\nfrom .source_address_validator import SourceAddressValidator\n\n__all__ = [\n    # Base classes\n    \"ChallengeValidator\",\n    \"ChallengeContext\",\n    \"ValidationResult\",\n    \"ChallengeValidationError\",\n    \"ValidationTimeoutError\",\n    \"InvalidChallengeTypeError\",\n    # Registry\n    \"ChallengeValidatorRegistry\",\n    # Validators\n    \"HttpChallengeValidator\",\n    \"DnsChallengeValidator\",\n    \"TlsAlpnChallengeValidator\",\n    \"EmailReplyChallengeValidator\",\n    \"TkauthChallengeValidator\",\n    \"SourceAddressValidator\",\n]\n"
  },
  {
    "path": "acme_srv/challenge_validators/base.py",
    "content": "\"\"\"\nBase classes and common structures for challenge validators.\n\nThis module contains the abstract base classes, data structures, and exceptions\nused across all challenge validator implementations.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Dict, Any, List, Optional\nfrom dataclasses import dataclass\nimport logging\n\n\n@dataclass\nclass ValidationResult:\n    \"\"\"Structured result from challenge validation.\"\"\"\n\n    success: bool\n    invalid: bool\n    error_message: Optional[str] = None\n    details: Optional[Dict[str, Any]] = None\n\n\n@dataclass\nclass ChallengeContext:\n    \"\"\"Context information for challenge validation.\"\"\"\n\n    challenge_name: str\n    token: str\n    jwk_thumbprint: str\n    authorization_type: str  # 'dns' or 'ip'\n    authorization_value: str\n    keyauthorization: Optional[str] = None\n    dns_servers: Optional[List[str]] = None\n    proxy_servers: Optional[Dict[str, str]] = None\n    timeout: int = 10\n    source_address: Optional[str] = None  # For source address validation\n    options: Optional[Dict[str, Any]] = None  # Additional options\n\n\nclass ChallengeValidationError(Exception):\n    \"\"\"Base exception for challenge validation errors.\"\"\"\n\n    pass  # pragma: no cover\n\n\nclass ValidationTimeoutError(ChallengeValidationError):\n    \"\"\"Raised when validation times out.\"\"\"\n\n    pass  # pragma: no cover\n\n\nclass InvalidChallengeTypeError(ChallengeValidationError):\n    \"\"\"Raised when an unsupported challenge type is encountered.\"\"\"\n\n    pass  # pragma: no cover\n\n\nclass ChallengeValidator(ABC):\n    \"\"\"Abstract base class for all challenge validators.\"\"\"\n\n    def __init__(self, logger: logging.Logger):\n        self.logger = logger\n\n    @abstractmethod\n    def get_challenge_type(self) -> str:\n        \"\"\"Return the challenge type this validator handles (e.g., 'http-01').\"\"\"\n        pass  # pragma: no cover\n\n    @abstractmethod\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"\n        Perform the actual validation logic for this challenge type.\n\n        Args:\n            context: Challenge context containing all necessary information\n\n        Returns:\n            ValidationResult: Structured result with success/failure status\n        \"\"\"\n        pass  # pragma: no cover\n\n    def validate_challenge(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"\n        Main entry point for validation with error handling and logging.\n\n        Args:\n            context: Challenge context containing all necessary information\n\n        Returns:\n            ValidationResult: Structured result with success/failure status\n        \"\"\"\n        self.logger.debug(\n            \"Starting %s validation for challenge: %s\",\n            self.get_challenge_type(),\n            context.challenge_name,\n        )\n\n        try:\n            result = self.perform_validation(context)\n            self.logger.debug(\n                \"%s validation completed for %s: success=%s, invalid=%s, error=%s\",\n                self.get_challenge_type(),\n                context.challenge_name,\n                result.success,\n                result.invalid,\n                result.error_message if result.error_message else \"None\",\n            )\n            return result\n        except Exception as e:\n            self.logger.error(\n                \"%s validation failed for %s: %s\",\n                self.get_challenge_type(),\n                context.challenge_name,\n                str(e),\n            )\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=str(e),\n                details={\"exception_type\": type(e).__name__},\n            )\n"
  },
  {
    "path": "acme_srv/challenge_validators/dns_validator.py",
    "content": "\"\"\"\nDNS-01 Challenge Validator.\n\nImplements validation logic for DNS-01 challenges according to RFC 8555.\n\"\"\"\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass DnsChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for DNS-01 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        return \"dns-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform DNS-01 challenge validation.\"\"\"\n        self.logger.debug(\"DnsChallengeValidator.perform_validation()\")\n        try:\n            from acme_srv.helper import b64_url_encode, sha256_hash, txt_get\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Required dependencies not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        # Handle wildcard domain\n        fqdn = self._handle_wildcard_domain(context.authorization_value)\n\n        # Construct the DNS record name\n        dns_record_name = f\"_acme-challenge.{fqdn}\"\n\n        # Compute expected hash\n        expected_hash = b64_url_encode(\n            self.logger,\n            sha256_hash(self.logger, f\"{context.token}.{context.jwk_thumbprint}\"),\n        )\n\n        # Query DNS\n        txt_records = txt_get(self.logger, dns_record_name, context.dns_servers)\n\n        if expected_hash in txt_records:\n            success = True\n        else:\n            success = False\n            self.logger.debug(\n                \"DnsChallengeValidator.perform_validation(): Expected hash %s not found in DNS records: %s\",\n                expected_hash,\n                txt_records,\n            )\n\n        self.logger.debug(\n            \"DnsChallengeValidator.perform_validation() ended with: %s\", success\n        )\n        return ValidationResult(\n            success=success,\n            invalid=not success,\n            error_message=None\n            if success\n            else '{\"status\": 403, \"type\": \"urn:ietf:params:acme:error:incorrectResponse\", \"detail\": \"DNS record not found or incorrect\"}',\n            details={\n                \"dns_record\": dns_record_name,\n                \"expected_hash\": expected_hash,\n                \"found_records\": txt_records,\n            },\n        )\n\n    def _handle_wildcard_domain(self, fqdn: str) -> str:\n        \"\"\"Handle wildcard domain by removing the '*.' prefix.\"\"\"\n        self.logger.debug(\n            \"DnsChallengeValidator._handle_wildcard_domain() called with: %s\", fqdn\n        )\n        if fqdn.startswith(\"*.\"):\n            fqdn = fqdn[2:]\n            self.logger.debug(\n                \"DnsChallengeValidator._handle_wildcard_domain(): Wildcard domain detected, updated FQDN: %s\",\n                fqdn,\n            )\n        self.logger.debug(\n            \"DnsChallengeValidator._handle_wildcard_domain() returning: %s\", fqdn\n        )\n        return fqdn\n"
  },
  {
    "path": "acme_srv/challenge_validators/email_reply_validator.py",
    "content": "\"\"\"\nEmail Reply Challenge Validator.\n\nImplements validation logic for email-reply-00 challenges.\n\"\"\"\nfrom typing import Tuple\nimport re\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\nfrom acme_srv.helper import b64_url_encode, convert_byte_to_string, sha256_hash\n\n\nclass EmailReplyChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for email-reply-00 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        \"\"\"Return the challenge type this validator handles.\"\"\"\n        return \"email-reply-00\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform email-reply-00 challenge validation.\"\"\"\n        self.logger.debug(\"EmailReplyChallengeValidator.perform_validation()\")\n        try:\n            from acme_srv.email_handler import EmailHandler\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Email handler not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        calculated_keyauth, rfc_token1 = self._generate_email_keyauth(\n            context.challenge_name,\n            context.token,\n            context.jwk_thumbprint,\n            context.keyauthorization,\n        )\n\n        with EmailHandler(debug=False, logger=self.logger) as email_handler:\n            email_receive = email_handler.receive(\n                callback=lambda email_data: self._filter_email(email_data, rfc_token1)\n            )\n\n            if not email_receive or \"body\" not in email_receive:\n                return ValidationResult(\n                    success=False,\n                    invalid=False,\n                    error_message=\"No email received or email body missing\",\n                )\n\n            email_keyauth = self._extract_email_keyauth(email_receive[\"body\"])\n\n            if (\n                email_keyauth\n                and calculated_keyauth\n                and email_keyauth == calculated_keyauth\n            ):\n                self.logger.debug(\n                    \"EmailReplyChallengeValidator.perform_validation() complete\"\n                )\n                return ValidationResult(\n                    success=True,\n                    invalid=False,\n                    details={\"calculated_keyauth\": calculated_keyauth},\n                )\n            else:\n                self.logger.error(\n                    \"Email keyauthorization does not match calculated keyauthorization\"\n                )\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=\"Email keyauthorization mismatch\",\n                    details={\"expected\": calculated_keyauth, \"received\": email_keyauth},\n                )\n\n    def _generate_email_keyauth(\n        self, challenge_name: str, rfc_token2: str, jwk_thumbprint: str, rfc_token1: str\n    ) -> Tuple[str, str]:\n        \"\"\"Generate email keyauthorization - placeholder for actual implementation.\"\"\"\n        self.logger.debug(\n            \"EmailReplyChallengeValidator._generate_email_keyauth() for %s\",\n            challenge_name,\n        )\n\n        calculated_keyauth = convert_byte_to_string(\n            b64_url_encode(\n                self.logger,\n                sha256_hash(self.logger, f\"{rfc_token1}{rfc_token2}.{jwk_thumbprint}\"),\n            )\n        )\n        return calculated_keyauth, rfc_token1\n\n    def _filter_email(self, email_data, rfc_token1):\n\n        filter_string = f\"ACME: {rfc_token1}\"\n        self.logger.debug(\n            \"Challenge._validate_email_reply_challenge(): filter string: %s\",\n            filter_string,\n        )\n\n        if filter_string in email_data.get(\"subject\", \"\"):\n            self.logger.debug(\n                \"Challenge._validate_email_reply_challenge(): email subject matches filter: %s\",\n                email_data[\"subject\"],\n            )\n            return email_data\n        else:\n            self.logger.debug(\n                \"Challenge._validate_email_reply_challenge(): email subject does not match filter: %s\",\n                email_data.get(\"subject\", \"\"),\n            )\n            return None\n\n    def _extract_email_keyauth(self, email_body: str) -> str:\n        \"\"\"Extract keyauthorization from email body - placeholder for actual implementation.\"\"\"\n        self.logger.debug(\"EmailReplyChallengeValidator._extract_email_keyauth()\")\n        email_keyauthorization = None\n        if email_body:\n            # extract keyauthorization from email body\n            match = re.search(\n                r\"-+BEGIN ACME RESPONSE-+\\s*([\\w=+/ -]+)\\s*-+END ACME RESPONSE-+\",\n                email_body,\n                re.DOTALL,\n            )\n            if match:\n                email_keyauthorization = match.group(1).strip()\n\n        self.logger.debug(\n            \"Challenge._emailchallenge_keyauth_extract() ended with: %s\",\n            bool(email_keyauthorization),\n        )\n        return email_keyauthorization\n"
  },
  {
    "path": "acme_srv/challenge_validators/http_validator.py",
    "content": "\"\"\"\nHTTP-01 Challenge Validator.\n\nImplements validation logic for HTTP-01 challenges according to RFC 8555.\n\"\"\"\nimport json\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass HttpChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for HTTP-01 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        return \"http-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform HTTP-01 challenge validation.\"\"\"\n        # Import here to avoid circular imports and missing dependencies\n        try:\n            from acme_srv.helper import fqdn_resolve, ip_validate, proxy_check, url_get\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Required dependencies not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        # Determine if we're dealing with DNS or IP\n        if context.authorization_type == \"dns\":\n            _, invalid, error_msg = fqdn_resolve(\n                self.logger, context.authorization_value, context.dns_servers\n            )\n            if invalid:\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:dns\",\n                            \"detail\": f\"DNS resolution failed: {error_msg}\"\n                            if error_msg\n                            else \"DNS resolution failed\",\n                        }\n                    ),\n                    details={\"fqdn\": context.authorization_value},\n                )\n        elif context.authorization_type == \"ip\":\n            _, invalid = ip_validate(self.logger, context.authorization_value)\n            if invalid:\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:malformed\",\n                            \"detail\": f\"Invalid IP address: {context.authorization_value}\",\n                        }\n                    ),\n                    details={\"ip\": context.authorization_value},\n                )\n        else:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=json.dumps(\n                    {\n                        \"status\": 400,\n                        \"type\": \"urn:ietf:params:acme:error:unsupported\",\n                        \"detail\": f\"Unsupported authorization type: {context.authorization_type}\",\n                    }\n                ),\n                details={\"type\": context.authorization_type},\n            )\n\n        # Check for proxy configuration\n        proxy_server = None\n        if context.proxy_servers:\n            proxy_server = proxy_check(\n                self.logger, context.authorization_value, context.proxy_servers\n            )\n\n        # Perform HTTP request\n        url = f\"http://{context.authorization_value}/.well-known/acme-challenge/{context.token}\"\n        req, status_code, error_msg = url_get(\n            self.logger,\n            url,\n            dns_server_list=context.dns_servers,\n            proxy_server=proxy_server,\n            verify=False,\n            timeout=context.timeout,\n        )\n        if not req or status_code != 200:\n            return ValidationResult(\n                success=False,\n                invalid=False,\n                error_message=json.dumps(\n                    {\n                        \"status\": 403,\n                        \"type\": \"urn:ietf:params:acme:error:connection\",\n                        \"detail\": f\"HTTP request failed: {status_code} {error_msg}\",\n                    }\n                ),\n                details={\"url\": url},\n            )\n\n        response_got = req.splitlines()[0]\n        response_expected = f\"{context.token}.{context.jwk_thumbprint}\"\n\n        success = response_got == response_expected\n        return ValidationResult(\n            success=success,\n            invalid=not success,\n            error_message=None\n            if success\n            else json.dumps(\n                {\n                    \"status\": 403,\n                    \"type\": \"urn:ietf:params:acme:error:incorrectResponse\",\n                    \"detail\": \"Keyauthorization mismatch\",\n                }\n            ),\n            details={\n                \"expected\": response_expected,\n                \"received\": response_got,\n                \"url\": url,\n            },\n        )\n"
  },
  {
    "path": "acme_srv/challenge_validators/registry.py",
    "content": "\"\"\"\nChallenge Validator Registry.\n\nProvides a registry system for managing and accessing challenge validators.\n\"\"\"\nfrom typing import Dict, List, Optional\nimport logging\nfrom .base import (\n    ChallengeValidator,\n    ChallengeContext,\n    ValidationResult,\n    InvalidChallengeTypeError,\n)\n\n\nclass ChallengeValidatorRegistry:\n    \"\"\"Registry for managing challenge validators.\"\"\"\n\n    def __init__(self, logger: logging.Logger):\n        self.logger = logger\n        self._validators: Dict[str, ChallengeValidator] = {}\n\n    def register_validator(self, validator: ChallengeValidator) -> None:\n        \"\"\"Register a challenge validator.\"\"\"\n        self.logger.debug(\"ChallengeValidatorRegistry.register_validator()\")\n        challenge_type = validator.get_challenge_type()\n        self._validators[challenge_type] = validator\n        self.logger.debug(\n            \"ChallengeValidatorRegistry.register_validator(): Registered validator for challenge type: %s\",\n            challenge_type,\n        )\n\n    def get_validator(self, challenge_type: str) -> Optional[ChallengeValidator]:\n        \"\"\"Get a validator for the specified challenge type.\"\"\"\n        self.logger.debug(\n            \"ChallengeValidatorRegistry.get_validator(%s)\", challenge_type\n        )\n        return self._validators.get(challenge_type)\n\n    def get_supported_types(self) -> List[str]:\n        \"\"\"Get list of supported challenge types.\"\"\"\n        self.logger.debug(\"ChallengeValidatorRegistry.get_supported_types()\")\n        return list(self._validators.keys())\n\n    def is_supported(self, challenge_type: str) -> bool:\n        \"\"\"Check if a challenge type is supported.\"\"\"\n        self.logger.debug(\"ChallengeValidatorRegistry.is_supported(%s)\", challenge_type)\n        return challenge_type in self._validators\n\n    def validate_challenge(\n        self, challenge_type: str, context: ChallengeContext\n    ) -> ValidationResult:\n        \"\"\"Validate a challenge using the appropriate validator.\"\"\"\n        self.logger.debug(\n            \"ChallengeValidatorRegistry.validate_challenge(%s)\", challenge_type\n        )\n        validator = self.get_validator(challenge_type)\n        if not validator:\n            raise InvalidChallengeTypeError(\n                f\"Unsupported challenge type: {challenge_type}\"\n            )\n\n        return validator.validate_challenge(context)\n"
  },
  {
    "path": "acme_srv/challenge_validators/source_address_validator.py",
    "content": "\"\"\"\nSource Address Validator.\n\nImplements source address validation for challenges, including forward and reverse\naddress checking capabilities.\n\"\"\"\nfrom typing import Dict, Any, List\nimport json\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass SourceAddressValidator(ChallengeValidator):\n    \"\"\"Validator for source address checks across all challenge types.\"\"\"\n\n    def __init__(\n        self, logger, forward_check: bool = False, reverse_check: bool = False\n    ):\n        super().__init__(logger)\n        self.forward_check = forward_check\n        self.reverse_check = reverse_check\n\n    def get_challenge_type(self) -> str:\n        return \"source-address\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform source address validation.\"\"\"\n        self.logger.debug(\"SourceAddressValidator.perform_validation() called\")\n        # Import here to avoid circular imports\n        try:\n            from acme_srv.helper import fqdn_resolve, ip_validate, ptr_resolve\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Required dependencies not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        self.logger.debug(\n            \"SourceAddressValidator.perform_validation(): source address validation for %s (forward: %s, reverse: %s)\",\n            context.authorization_value,\n            self.forward_check,\n            self.reverse_check,\n        )\n\n        # Update forward and reverse check settings from context options if available\n        if context.options:\n            self.forward_check = context.options.get(\n                \"forward_address_check\", self.forward_check\n            )\n            self.reverse_check = context.options.get(\n                \"reverse_address_check\", self.reverse_check\n            )\n\n        # Get source address from context\n        source_address = getattr(context, \"source_address\", None)\n        if not source_address:\n            return ValidationResult(\n                success=True,\n                invalid=False,\n                details={\"message\": \"No source address provided, skipping validation\"},\n            )\n\n        validation_details = {\n            \"source_address\": source_address,\n            \"authorization_value\": context.authorization_value,\n            \"forward_check\": self.forward_check,\n            \"reverse_check\": self.reverse_check,\n        }\n\n        # Perform forward address check\n        if self.forward_check:\n            self.logger.debug(\n                \"SourceAddressValidator.perform_validation(): Performing forward address check\"\n            )\n            forward_result = self._perform_forward_check(\n                context.authorization_value, source_address, context.dns_servers\n            )\n            validation_details.update(forward_result)\n            if not forward_result.get(\"forward_check_passed\", False):\n\n                self.logger.debug(\n                    \"SourceAddressValidator.perform_validation(): Forward address check failed\"\n                )\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n                            \"detail\": f\"Forward check failed: {forward_result.get('error', 'Forward address check failed')}\",\n                        }\n                    ),\n                    details=validation_details,\n                )\n\n        # Perform reverse address check\n        if self.reverse_check:\n            self.logger.debug(\n                \"SourceAddressValidator.perform_validation(): Performing reverse address check\"\n            )\n            reverse_result = self._perform_reverse_check(\n                context.authorization_value, source_address, context.dns_servers\n            )\n            validation_details.update(reverse_result)\n\n            if not reverse_result.get(\"reverse_check_passed\", False):\n                self.logger.debug(\n                    \"SourceAddressValidator.perform_validation(): Reverse address check failed\"\n                )\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n                            \"detail\": f\"Reverse check failed: {reverse_result.get('error', 'Reverse address check failed')}\",\n                        }\n                    ),\n                    details=validation_details,\n                )\n\n        return ValidationResult(success=True, invalid=False, details=validation_details)\n\n    def _perform_forward_check(\n        self, domain: str, source_address: str, dns_servers: List[str]\n    ) -> Dict[str, Any]:\n        \"\"\"Perform forward DNS lookup to verify source address.\"\"\"\n        self.logger.debug(\n            \"SourceAddressValidator._perform_forward_check(): Performing forward address check: %s -> %s\",\n            domain,\n            source_address,\n        )\n\n        try:\n            from acme_srv.helper import fqdn_resolve\n\n            # Resolve the domain to IP addresses\n            resolved_ips, _invalid, error_message = fqdn_resolve(\n                logger=self.logger, host=domain, dnssrv=dns_servers, catch_all=True\n            )\n\n            if error_message:\n                self.logger.error(\n                    \"Forward address check DNS resolution failed: %s\", error_message\n                )\n                return {\n                    \"forward_check_passed\": False,\n                    \"error\": error_message,\n                    \"domain\": domain,\n                }\n            else:\n                self.logger.debug(\n                    \"SourceAddressValidator._perform_forward_check(): Resolved IPs for %s: %s\",\n                    domain,\n                    resolved_ips,\n                )\n                # Check if source address matches any resolved IP\n                forward_check_passed = source_address in resolved_ips\n                self.logger.debug(\n                    \"SourceAddressValidator._perform_forward_check(): Forward check %s for %s\",\n                    \"passed\" if forward_check_passed else \"failed\",\n                    domain,\n                )\n                result = {\n                    \"forward_check_passed\": forward_check_passed,\n                    \"resolved_ips\": resolved_ips,\n                    \"domain\": domain,\n                }\n                if not forward_check_passed:\n                    self.logger.debug(\n                        \"SourceAddressValidator._perform_forward_check(): Source address not found in resolved IPs\"\n                    )\n                    result[\"error\"] = \"Source address not found in resolved IPs\"\n                return result\n\n        except Exception as e:\n            self.logger.error(\"Forward address check failed: %s\", str(e))\n            return {\"forward_check_passed\": False, \"error\": str(e), \"domain\": domain}\n\n    def _perform_reverse_check(\n        self, domain: str, source_address: str, dns_servers: List[str]\n    ) -> Dict[str, Any]:\n        \"\"\"Perform reverse DNS lookup to verify domain ownership.\"\"\"\n        self.logger.debug(\n            \"SourceAddressValidator._perform_reverse_check(): Performing reverse address check: %s -> %s\",\n            source_address,\n            domain,\n        )\n        try:\n            from acme_srv.helper import ptr_resolve\n\n            # Perform reverse lookup on source address\n            reverse_domains = ptr_resolve(\n                self.logger, source_address, dnssrv=dns_servers\n            )\n\n            # Check if any reverse domain matches or is a subdomain of the requested domain\n            reverse_check_passed = any(\n                self._domain_matches(domain, reverse_domain)\n                for reverse_domain in reverse_domains\n            )\n            self.logger.debug(\n                \"SourceAddressValidator._perform_reverse_check(): Reverse check %s for %s\",\n                \"passed\" if reverse_check_passed else \"failed\",\n                domain,\n            )\n\n            result = {\n                \"reverse_check_passed\": reverse_check_passed,\n                \"reverse_domains\": reverse_domains,\n                \"source_address\": source_address,\n            }\n            if not reverse_check_passed:\n                # set error detail if no matches found\n                self.logger.debug(\n                    \"SourceAddressValidator._perform_reverse_check(): No matching reverse domains found: %s\",\n                    reverse_domains,\n                )\n                result[\"error\"] = \"No matching domains found\"\n\n            return result\n\n        except Exception as e:\n            self.logger.error(\"Reverse address check failed: %s\", str(e))\n            return {\n                \"reverse_check_passed\": False,\n                \"error\": str(e),\n                \"source_address\": source_address,\n            }\n\n    def _domain_matches(self, requested_domain: str, resolved_domain: str) -> bool:\n        \"\"\"Check if domains match (exact or subdomain).\"\"\"\n        if requested_domain:\n            requested_domain = requested_domain.lower().rstrip(\".\")\n        if resolved_domain:\n            resolved_domain = resolved_domain.lower().rstrip(\".\")\n\n        # Exact match\n        if requested_domain == resolved_domain:\n            return True\n\n        if not resolved_domain:\n            return False\n        # Subdomain match (resolved domain ends with requested domain)\n        return resolved_domain.endswith(\".\" + requested_domain)\n"
  },
  {
    "path": "acme_srv/challenge_validators/tkauth_validator.py",
    "content": "\"\"\"\nTKAuth Challenge Validator.\n\nImplements validation logic for tkauth-01 challenges.\n\"\"\"\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass TkauthChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for tkauth-01 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        return \"tkauth-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform tkauth-01 challenge validation.\"\"\"\n        # For now, this always returns success as in the original implementation\n        # This would be expanded with actual validation logic when requirements are defined\n\n        self.logger.debug(\n            \"TKAuth validation for challenge %s with authorization value %s\",\n            context.challenge_name,\n            context.authorization_value,\n        )\n\n        return ValidationResult(\n            success=True,\n            invalid=False,\n            details={\n                \"validation_type\": \"tkauth-01\",\n                \"authorization_value\": context.authorization_value,\n            },\n        )\n"
  },
  {
    "path": "acme_srv/challenge_validators/tls_alpn_validator.py",
    "content": "\"\"\"\nTLS-ALPN-01 Challenge Validator.\n\nImplements validation logic for TLS-ALPN-01 challenges according to RFC 8737.\n\"\"\"\nimport json\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass TlsAlpnChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for TLS-ALPN-01 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        return \"tls-alpn-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform TLS-ALPN-01 challenge validation.\"\"\"\n        self.logger.debug(\"TlsAlpnChallengeValidator.perform_validation()\")\n        try:\n            from acme_srv.helper import (\n                fqdn_resolve,\n                ip_validate,\n                proxy_check,\n                servercert_get,\n                sha256_hash_hex,\n                b64_encode,\n            )\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Required dependencies not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        # Determine SNI value\n        if context.authorization_type == \"dns\":\n            _, invalid, error_msg = fqdn_resolve(\n                self.logger, context.authorization_value, context.dns_servers\n            )\n            if invalid:\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:dns\",\n                            \"detail\": f\"DNS resolution failed: {error_msg}\"\n                            if error_msg\n                            else \"DNS resolution failed\",\n                        }\n                    ),\n                )\n            sni = context.authorization_value\n        elif context.authorization_type == \"ip\":\n            sni, invalid = ip_validate(self.logger, context.authorization_value)\n            if invalid:\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=json.dumps(\n                        {\n                            \"status\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:malformed\",\n                            \"detail\": f\"Invalid IP address: {context.authorization_value}\",\n                        }\n                    ),\n                    details={\"ip\": context.authorization_value},\n                )\n        else:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=json.dumps(\n                    {\n                        \"status\": 400,\n                        \"type\": \"urn:ietf:params:acme:error:unsupported\",\n                        \"detail\": f\"Unsupported authorization type: {context.authorization_type}\",\n                    }\n                ),\n                details={\"type\": context.authorization_type},\n            )\n\n        # Compute expected extension value\n        sha256_digest = sha256_hash_hex(\n            self.logger, f\"{context.token}.{context.jwk_thumbprint}\"\n        )\n        extension_value = b64_encode(\n            self.logger, bytearray.fromhex(f\"0420{sha256_digest}\")\n        )\n\n        # Check for proxy configuration\n        proxy_server = None\n        if context.proxy_servers:\n            proxy_server = proxy_check(\n                self.logger, context.authorization_value, context.proxy_servers\n            )\n\n        # Get server certificate\n        cert = servercert_get(\n            self.logger, context.authorization_value, 443, proxy_server, sni\n        )\n\n        if not cert:\n            return ValidationResult(\n                success=False,\n                invalid=False,\n                error_message=json.dumps(\n                    {\n                        \"status\": 400,\n                        \"type\": \"urn:ietf:params:acme:error:incorrectResponse\",\n                        \"detail\": f\"Unable to retrieve server certificate for {context.authorization_value}\",\n                    }\n                ),\n            )\n\n        # Validate certificate extensions\n        success = self._validate_certificate_extensions(\n            cert, extension_value, context.authorization_value\n        )\n\n        self.logger.debug(\n            \"TlsAlpnChallengeValidator.perform_validation() ended with: %s\", success\n        )\n        return ValidationResult(\n            success=success,\n            invalid=not success,\n            error_message=None\n            if success\n            else json.dumps(\n                {\n                    \"status\": 403,\n                    \"type\": \"urn:ietf:params:acme:error:incorrectResponse\",\n                    \"detail\": \"Certificate extension validation failed\",\n                }\n            ),\n            details={\"expected_extension\": extension_value, \"sni\": sni},\n        )\n\n    def _validate_certificate_extensions(\n        self, cert: str, extension_value: str, fqdn: str\n    ) -> bool:\n        \"\"\"Validate certificate extensions for TLS-ALPN challenge.\"\"\"\n        self.logger.debug(\n            \"TlsAlpnChallengeValidator._validate_certificate_extensions()\"\n        )\n        try:\n            from acme_srv.helper import (\n                cert_san_get,\n                fqdn_in_san_check,\n                cert_extensions_get,\n            )\n        except ImportError:\n            self.logger.error(\n                \"Required helper functions not available for certificate validation\"\n            )\n            return False\n\n        san_list = cert_san_get(self.logger, cert, recode=False)\n        fqdn_in_san = fqdn_in_san_check(self.logger, san_list, fqdn)\n\n        if not fqdn_in_san:\n            self.logger.debug(\n                \"TlsAlpnChallengeValidator._validate_certificate_extensions(): FQDN check against SAN failed\"\n            )\n            return False\n\n        extension_list = cert_extensions_get(self.logger, cert, recode=False)\n        if extension_value in extension_list:\n            self.logger.debug(\n                \"TlsAlpnChallengeValidator._validate_certificate_extensions(): TLS-ALPN validation successful\"\n            )\n            return True\n        else:\n            self.logger.debug(\n                \"TlsAlpnChallengeValidator._validate_certificate_extensions(): TLS-ALPN validation not successful\"\n            )\n            return False\n"
  },
  {
    "path": "acme_srv/directory.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Directory class\"\"\"\n# pylint: disable=e0401, r0913, r1705\n\nfrom __future__ import print_function\nimport uuid\nimport json\nfrom typing import Dict, Optional, List, Tuple\nfrom dataclasses import dataclass, field\nfrom .version import __version__, __dbversion__\nfrom .helper import (\n    load_config,\n    ca_handler_load,\n    config_profile_load,\n    config_async_mode_load,\n)\nfrom .db_handler import DBstore\n\nGH_HOME = \"https://github.com/grindsa/acme2certifier\"\n\n\n@dataclass\nclass DirectoryConfig:\n    \"\"\"Configuration dataclass for Directory settings and parameters.\"\"\"\n\n    supress_version: bool = False\n    db_check: bool = False\n    suppress_product_information: bool = False\n    tos_url: Optional[str] = None\n    url_prefix: str = \"\"\n    home: str = GH_HOME\n    caaidentities: List[str] = field(default_factory=list)\n    profiles: Dict = field(default_factory=dict)\n    eab: bool = False\n    acme_url: Optional[str] = None\n    profiles_sync: bool = False\n    profiles_sync_interval: int = 604800  # default: 7 days\n    async_mode: bool = False\n\n\nclass DirectoryRepository:\n    \"\"\"Repository for all Directory-related database access.\"\"\"\n\n    def __init__(self, dbstore: object, logger: object) -> None:\n        \"\"\"Initialize DirectoryRepository with dbstore and logger.\"\"\"\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def get_db_version(self) -> Tuple[Optional[str], Optional[str]]:\n        \"\"\"Get the current database version from the DBstore.\"\"\"\n        try:\n            return self.dbstore.dbversion_get()\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to check database version: %s\", err\n            )\n            return None, None\n\n    def profile_list_get(self) -> List[Dict[str, object]]:\n        \"\"\"Get the list of profiles from the database.\"\"\"\n        try:\n            profiles = self.dbstore.hkparameter_get(\"profiles\")\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to get profile list: %s\", err)\n            return []\n        if profiles:\n            try:\n                return json.loads(profiles)\n            except Exception as err_:\n                self.logger.error(\n                    \"Error when loading the profiles parameter from database: %s\", err_\n                )\n                return []\n        return []\n\n    def profile_list_set(self, data_dic: Dict[str, object]) -> None:\n        \"\"\"Set the list of profiles in the database.\"\"\"\n        try:\n            self.dbstore.hkparameter_add(data_dic)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to set profile list: %s\", err)\n\n\nclass Directory:\n    \"\"\"Main handler for ACME Directory logic, configuration, and response building.\"\"\"\n\n    def __init__(\n        self,\n        debug: Optional[object] = None,\n        srv_name: Optional[str] = None,\n        logger: Optional[object] = None,\n    ) -> None:\n        \"\"\"Initialize Directory with configuration, repository, and logger.\"\"\"\n        self.server_name = srv_name\n        self.logger = logger\n        self.dbstore = DBstore(debug, self.logger)\n        self.repository = DirectoryRepository(self.dbstore, self.logger)\n        self.config = DirectoryConfig()\n        self.cahandler = None\n        self.version = __version__\n        self.dbversion = __dbversion__\n\n    def __enter__(self) -> \"Directory\":\n        \"\"\"Enter context manager for Directory.\"\"\"\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args) -> None:\n        \"\"\"Exit context manager for Directory.\"\"\"\n        # pylint: disable=w0107\n        pass\n\n    def _load_configuration(self) -> None:\n        \"\"\"Load and parse all Directory configuration from file and environment.\"\"\"\n        self.logger.debug(\"Directory._load_configuration()\")\n        config_dic = load_config(self.logger, \"Directory\")\n\n        self._parse_directory_section(config_dic)\n        self._parse_booleans(config_dic)\n        self._parse_eab_and_profiles(config_dic)\n        self._parse_cahandler_section(config_dic)\n        self._load_ca_handler(config_dic)\n        self.config.async_mode = config_async_mode_load(\n            self.logger, config_dic, self.dbstore.type\n        )\n        self.logger.debug(\"Directory._load_configuration() ended\")\n\n    def _parse_directory_section(self, config_dic: object) -> None:\n        \"\"\"Parse the [Directory] section for basic config values.\"\"\"\n        if \"Directory\" in config_dic:\n            cfg_dic = dict(config_dic[\"Directory\"])\n            self.config.tos_url = cfg_dic.get(\"tos_url\", None)\n            self.config.url_prefix = cfg_dic.get(\"url_prefix\", \"\")\n            self.config.home = cfg_dic.get(\"home\", GH_HOME)\n            tmp_caaidentities = config_dic.get(\n                \"Directory\", \"caaidentities\", fallback=None\n            )\n            if tmp_caaidentities:\n                self.config.caaidentities = self._parse_caaidentities(tmp_caaidentities)\n\n    def _parse_caaidentities(self, value: str) -> List[str]:\n        \"\"\"Parse the caaIdentities config value as JSON or fallback to list.\"\"\"\n        try:\n            return json.loads(value)\n        except Exception as err_:\n            if \"[\" not in value and '\"' not in value:\n                return [value]\n            else:\n                self.logger.error(\n                    \"Error when loading the caaIdentities parameter from config: %s\",\n                    err_,\n                )\n                return []\n\n    def _parse_booleans(self, config_dic: object) -> None:\n        \"\"\"Parse boolean config values for Directory settings.\"\"\"\n        for key, attr in [\n            (\"supress_version\", \"supress_version\"),\n            (\"db_check\", \"db_check\"),\n            (\"suppress_product_information\", \"suppress_product_information\"),\n        ]:\n            try:\n                setattr(\n                    self.config,\n                    attr,\n                    config_dic.getboolean(\n                        \"Directory\", key, fallback=getattr(self.config, attr)\n                    ),\n                )\n            except Exception as err_:\n                self.logger.error(\"%s not set: %s\", key, err_)\n\n    def _parse_eab_and_profiles(self, config_dic: object) -> None:\n        \"\"\"Parse EAB handler and profile configuration.\"\"\"\n        if (\n            \"EABhandler\" in config_dic\n            and \"eab_handler_file\" in config_dic[\"EABhandler\"]\n        ):\n            self.config.eab = True\n        self.config.profiles = config_profile_load(self.logger, config_dic)\n\n    def _parse_cahandler_section(self, config_dic: object) -> None:\n        \"\"\"Parse the [CAHandler] section for ACME URL and profile sync settings.\"\"\"\n        self.logger.debug(\"Directory._parse_cahandler_section()\")\n        if \"CAhandler\" in config_dic:\n            cfg_dic = dict(config_dic[\"CAhandler\"])\n            self.config.acme_url = cfg_dic.get(\"acme_url\", None)\n            try:\n                self.config.profiles_sync = config_dic.getboolean(\n                    \"CAhandler\",\n                    \"profiles_sync\",\n                    fallback=self.config.profiles_sync,\n                )\n            except Exception as err_:\n                self.logger.error(\"profiles_sync not set: %s\", err_)\n\n            self._validate_profiles_sync()\n            self._set_profiles_sync_interval(config_dic)\n\n        self.logger.debug(\"Directory._parse_cahandler_section() ended\")\n\n    def _validate_profiles_sync(self) -> None:\n        if not self.config.profiles_sync:\n            return\n        if self.config.profiles:\n            self.logger.error(\n                \"Profiles are configured via acme_srv.cfg. Disabling profile sync.\"\n            )\n            self.config.profiles_sync = False\n        elif not self.config.acme_url:\n            self.logger.error(\"profiles_sync is set but no acme_url configured.\")\n            self.config.profiles_sync = False\n\n    def _set_profiles_sync_interval(self, config_dic: object) -> None:\n        if not self.config.profiles_sync:\n            return\n        try:\n            self.config.profiles_sync_interval = config_dic.getint(\n                \"CAhandler\",\n                \"profiles_sync_interval\",\n                fallback=self.config.profiles_sync_interval,\n            )\n        except Exception as err_:\n            self.logger.error(\"profiles_sync_interval not set: %s\", err_)\n        self.logger.debug(\n            \"Directory._parse_cahandler_section(): profiles_sync is enabled. Interval: %s seconds\",\n            self.config.profiles_sync_interval,\n        )\n\n    def _load_ca_handler(self, config_dic: object) -> None:\n        \"\"\"Load the CA handler module as configured.\"\"\"\n        ca_handler_module = ca_handler_load(self.logger, config_dic)\n        if ca_handler_module:\n            self.cahandler = ca_handler_module.CAhandler\n        else:\n            self.logger.critical(\"No ca_handler loaded\")\n\n    def _build_meta_information(self) -> Dict[str, object]:\n        \"\"\"Build the meta information dictionary for the directory response.\"\"\"\n        self.logger.debug(\"Directory._build_meta_information()\")\n        meta_dic = {}\n        if not self.config.suppress_product_information:\n            meta_dic = {\n                \"home\": self.config.home,\n                \"author\": \"grindsa <grindelsack@gmail.com>\",\n                \"name\": \"acme2certifier\",\n            }\n            if not self.config.supress_version:\n                meta_dic[\"version\"] = self.version\n        else:\n            if self.config.home != GH_HOME:\n                meta_dic[\"home\"] = self.config.home\n        if self.config.tos_url:\n            meta_dic[\"termsOfService\"] = self.config.tos_url\n        if self.config.caaidentities:\n            meta_dic[\"caaIdentities\"] = self.config.caaidentities\n        if self.config.profiles:\n            meta_dic[\"profiles\"] = self.config.profiles\n        if self.config.eab:\n            meta_dic[\"externalAccountRequired\"] = True\n        self.logger.debug(\"Directory._build_meta_information() ended\")\n        return meta_dic\n\n    def _build_directory_response(self) -> Dict[str, object]:\n        \"\"\"Build the full directory response dictionary for the ACME directory endpoint.\"\"\"\n        self.logger.debug(\"Directory._build_directory_response()\")\n        d_dic = {\n            \"newAuthz\": self.server_name + self.config.url_prefix + \"/acme/new-authz\",\n            \"newNonce\": self.server_name + self.config.url_prefix + \"/acme/newnonce\",\n            \"newAccount\": self.server_name\n            + self.config.url_prefix\n            + \"/acme/newaccount\",\n            \"newOrder\": self.server_name + self.config.url_prefix + \"/acme/neworders\",\n            \"revokeCert\": self.server_name\n            + self.config.url_prefix\n            + \"/acme/revokecert\",\n            \"keyChange\": self.server_name + self.config.url_prefix + \"/acme/key-change\",\n            \"renewalInfo\": self.server_name\n            + self.config.url_prefix\n            + \"/acme/renewal-info\",\n            \"meta\": self._build_meta_information(),\n        }\n        if self.config.db_check:\n            version, _script_name = self.repository.get_db_version()\n            if version == self.dbversion:\n                d_dic[\"meta\"][\"db_check\"] = \"OK\"\n            else:\n                self.logger.error(\n                    \"Database schema mismatch detected: detected: %s/ expected: %s\",\n                    version,\n                    self.dbversion,\n                )\n                d_dic[\"meta\"][\"db_check\"] = \"NOK\"\n        # generate random key in json as recommended by LE\n        d_dic[\n            uuid.uuid4().hex\n        ] = \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\"\n        self.logger.debug(\"Directory._build_directory_response() ended\")\n        return d_dic\n\n    def get_directory_response(self) -> Dict[str, object]:\n        \"\"\"Public method to get the ACME directory response, including CA handler checks.\"\"\"\n        self.logger.debug(\"Directory.get_directory_response()\")\n        error = None\n\n        if self.cahandler:\n\n            with self.cahandler(None, self.logger) as ca_handler:\n                if hasattr(ca_handler, \"handler_check\"):\n                    error = ca_handler.handler_check()\n                if (\n                    self.config.profiles_sync\n                    and hasattr(ca_handler, \"synchronize_profiles\")\n                    and not error\n                ):\n                    self.config.profiles = ca_handler.synchronize_profiles(\n                        self.repository,\n                        self.config.acme_url,\n                        self.config.profiles_sync_interval,\n                        self.config.async_mode,\n                    )\n\n        else:\n            error = \"No handler loaded\"\n\n        if not error:\n            d_dic = self._build_directory_response()\n        else:\n            self.logger.critical(\n                \"CA handler error during get_directory_response: %s\", error\n            )\n            d_dic = {\"error\": \"error in ca_handler configuration\"}\n        return d_dic\n\n    def directory_get(self) -> Dict[str, object]:\n        \"\"\"return response to ACME directory call\"\"\"\n        self.logger.debug(\"Directory.directory_get()\")\n        return self.get_directory_response()\n\n    def servername_get(self) -> str:\n        \"\"\"dumb function to return servername\"\"\"\n        self.logger.debug(\"Directory.servername_get()\")\n        return self.server_name\n"
  },
  {
    "path": "acme_srv/email_handler.py",
    "content": "\"\"\"email handler for ACME server\"\"\"\nimport time\nimport email\nimport smtplib\nimport imaplib\nimport threading\nfrom email.mime.text import MIMEText\nfrom email.mime.multipart import MIMEMultipart\nfrom typing import Dict, List, Callable, Optional, Any\nfrom acme_srv.helper import load_config\n\n\nclass EmailHandler:\n    \"\"\"Email handler class for sending and receiving emails\"\"\"\n\n    def __init__(self, debug: bool = False, logger=None):\n        \"\"\"Initialize EmailHandler\"\"\"\n        self.debug = debug\n        self.logger = logger\n\n        # IMAP configuration\n        self.imap_server = None\n        self.imap_port = 993\n        self.imap_use_ssl = True\n\n        # SMTP configuration\n        self.smtp_server = None\n        self.smtp_port = 587\n        self.smtp_use_tls = True\n\n        # Authentication\n        self.username = None\n        self.password = None\n        self.email_address = None\n\n        # Polling configuration\n        self.polling_timer = 60  # seconds\n        self.connection_timeout = 30  # seconds\n\n        # Polling control\n        self._polling_active = False\n        self._polling_thread = None\n        self._email_callback = None\n\n    def __enter__(self):\n        \"\"\"Enter context manager\"\"\"\n        self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Exit context manager\"\"\"\n        self.stop_polling()\n\n    def _config_load(self):\n        \"\"\"Load configuration from config file\"\"\"\n        self.logger.debug(\"EmailHandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"acme_srv.cfg\")\n\n        # Load from DEFAULT section\n        if \"DEFAULT\" in config_dic:\n            # IMAP configuration\n            self.imap_server = config_dic.get(\"DEFAULT\", \"imap_server\", fallback=None)\n            try:\n                self.imap_port = int(\n                    config_dic.get(\"DEFAULT\", \"imap_port\", fallback=993)\n                )\n            except ValueError as err:\n                self.logger.warning(\n                    \"Failed to parse imap_port from configuration. Using default 993. Error: %s\",\n                    err,\n                )\n                self.imap_port = 993\n\n            self.imap_use_ssl = config_dic.getboolean(\n                \"DEFAULT\", \"imap_use_ssl\", fallback=True\n            )\n\n            # SMTP configuration (fallback to IMAP server if not specified)\n            self.smtp_server = config_dic.get(\n                \"DEFAULT\", \"smtp_server\", fallback=self.imap_server\n            )\n            try:\n                self.smtp_port = int(\n                    config_dic.get(\"DEFAULT\", \"smtp_port\", fallback=587)\n                )\n            except ValueError as err:\n                self.logger.warning(\n                    \"Failed to parse smtp_port from configuration. Using default 587. Error: %s\",\n                    err,\n                )\n                self.smtp_port = 587\n\n            self.smtp_use_tls = config_dic.getboolean(\n                \"DEFAULT\", \"smtp_use_tls\", fallback=True\n            )\n\n            # Authentication\n            self.username = config_dic.get(\"DEFAULT\", \"username\", fallback=None)\n            if not self.username:\n                # Fallback to 'user' if 'username' is not set\n                self.logger.debug(\"Falling back to 'user' for username\")\n                self.username = config_dic.get(\"DEFAULT\", \"user\", fallback=None)\n            self.password = config_dic.get(\"DEFAULT\", \"password\", fallback=None)\n            self.email_address = config_dic.get(\n                \"DEFAULT\", \"email_address\", fallback=self.username\n            )\n\n            # Timing configuration\n            try:\n                self.polling_timer = int(\n                    config_dic.get(\"DEFAULT\", \"polling_timer\", fallback=60)\n                )\n            except ValueError as err:\n                self.logger.warning(\n                    \"Failed to parse polling_timer from configuration. Using default 60. Error: %s\",\n                    err,\n                )\n                self.polling_timer = 60\n\n            try:\n                self.connection_timeout = int(\n                    config_dic.get(\"DEFAULT\", \"connection_timeout\", fallback=30)\n                )\n            except ValueError as err:\n                self.logger.warning(\n                    \"Failed to parse connection_timeout from configuration. Using default 30. Error: %s\",\n                    err,\n                )\n                self.connection_timeout = 30\n        else:\n            self.logger.warning(\"DEFAULT configuration section not found\")\n\n        self.logger.debug(\"EmailHandler._config_load() ended\")\n\n    def send_email_challenge(self, to_address: str = None, token1: str = None):\n        \"\"\"send challenge email\"\"\"\n        self.logger.debug(\"Challenge._email_send(%s)\", to_address)\n        message_text = f\"\"\"\nThis is an automatically generated ACME challenge for the email address\n\"{to_address}\". If you did not request an S/MIME certificate for this\naddress, please disregard this message and consider taking appropriate\nsecurity precautions.\n\nIf you did initiate the request, your email client may be able to process\nthis challenge automatically. Alternatively, you may need to manually\ncopy the first token and paste it into the designated verification tool\nor application.\"\"\"\n\n        self.send(\n            to_address=to_address, subject=f\"ACME: {token1}\", message=message_text\n        )\n\n    def send(\n        self,\n        to_address: str,\n        subject: str,\n        message: str,\n        from_address: Optional[str] = None,\n        html_message: Optional[str] = None,\n    ) -> bool:\n        \"\"\"Send email via SMTP\"\"\"\n        self.logger.debug(\"EmailHandler.send()\")\n\n        if not self._smtp_config_validate():\n            return False\n\n        try:\n            # Create message\n            msg = MIMEMultipart(\"alternative\") if html_message else MIMEText(message)\n            msg[\"Subject\"] = subject\n            msg[\"From\"] = from_address or self.email_address\n            msg[\"To\"] = to_address\n\n            if html_message:\n                # Add both plain text and HTML parts\n                part1 = MIMEText(message, \"plain\")\n                part2 = MIMEText(html_message, \"html\")\n                msg.attach(part1)\n                msg.attach(part2)\n\n            # Connect to SMTP server\n            if self.smtp_use_tls:\n                server = smtplib.SMTP(\n                    self.smtp_server, self.smtp_port, timeout=self.connection_timeout\n                )\n                server.starttls()\n            else:\n                server = smtplib.SMTP_SSL(\n                    self.smtp_server, self.smtp_port, timeout=self.connection_timeout\n                )\n\n            # Authenticate and send\n            if self.username and self.password:\n                server.login(self.username, self.password)\n\n            server.send_message(msg)\n            server.quit()\n\n            self.logger.info(\"Email sent successfully to %s\", to_address)\n            return True\n\n        except Exception as err:\n            self.logger.error(\"Failed to send email: %s\", err)\n            return False\n\n    def receive(\n        self,\n        callback: Optional[Callable] = None,\n        folder: str = \"INBOX\",\n        mark_as_read: bool = True,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Receive emails via IMAP and return as dictionary\"\"\"\n        self.logger.debug(\"EmailHandler.receive()\")\n\n        if not self._imap_config_validate():\n            return []\n\n        try:\n            mail = self._imap_connect()\n            mail.login(self.username, self.password)\n            mail.select(folder)\n\n            emails = self._emails_fetch(mail, callback, mark_as_read)\n\n            mail.close()\n            mail.logout()\n\n            self.logger.debug(\n                \"EmailHandler.receive(): retrieved emails: %d\", bool(emails)\n            )\n            return emails\n\n        except Exception as err:\n            self.logger.error(\"Failed to receive emails: %s\", err)\n            return []\n\n    def _imap_connect(self):\n        \"\"\"Connect to IMAP server and set timeout.\"\"\"\n        self.logger.debug(\"EmailHandler._imap_connect()\")\n        if self.imap_use_ssl:\n            mail = imaplib.IMAP4_SSL(self.imap_server, self.imap_port)\n        else:\n            mail = imaplib.IMAP4(self.imap_server, self.imap_port)\n        mail.socket().settimeout(self.connection_timeout)\n        self.logger.debug(\"EmailHandler._imap_connect() ended\")\n        return mail\n\n    def _emails_fetch(self, mail, callback, mark_as_read):\n        \"\"\"Fetch unread emails and process them.\"\"\"\n        self.logger.debug(\"EmailHandler._emails_fetch()\")\n        emails = []\n        status, messages = mail.search(None, \"UNSEEN\")\n        if status != \"OK\":\n            return emails\n\n        email_ids = messages[0].split()\n        for email_id in email_ids:\n            status, msg_data = mail.fetch(email_id, \"(RFC822)\")\n            if status != \"OK\":\n                continue\n            email_body = msg_data[0][1]\n            email_message = email.message_from_bytes(email_body)\n            parsed_email = self._email_parse(email_message)\n\n            if callback:\n                result = callback(parsed_email)\n                if result:\n                    self.logger.info(\"Email passed filter: %s\", result[\"subject\"])\n                    emails = (\n                        result  # return this email only if callback returns a value\n                    )\n                    break\n                else:\n                    self.logger.debug(\n                        \"EmailHandler.receive(): email did not pass filter: %s\",\n                        parsed_email[\"subject\"],\n                    )\n            else:\n                emails.append(parsed_email)\n\n            # Mark as read/unread\n            if mark_as_read:\n                mail.store(email_id, \"+FLAGS\", \"\\\\Seen\")\n            else:\n                mail.store(email_id, \"-FLAGS\", \"\\\\Seen\")\n        self.logger.debug(\"EmailHandler._emails_fetch() ended\")\n        return emails\n\n    def start_polling(\n        self, callback: Callable, folder: str = \"INBOX\", mark_as_read: bool = True\n    ):\n        \"\"\"Start polling for emails in a separate thread\"\"\"\n        self.logger.debug(\"EmailHandler.start_polling()\")\n\n        if self._polling_active:\n            self.logger.warning(\"Email polling is already active\")\n            return\n\n        self._email_callback = callback\n        self._polling_active = True\n        self._polling_thread = threading.Thread(\n            target=self._polling_loop, args=(folder, mark_as_read)\n        )\n        self._polling_thread.daemon = True\n        self._polling_thread.start()\n\n        self.logger.info(\n            \"Email polling started with %d second interval\", self.polling_timer\n        )\n\n    def stop_polling(self):\n        \"\"\"Stop email polling\"\"\"\n        self.logger.debug(\"EmailHandler.stop_polling()\")\n\n        if self._polling_active:\n            self._polling_active = False\n            if self._polling_thread:\n                self._polling_thread.join(timeout=5)\n            self.logger.info(\"Email polling stopped\")\n\n    def _polling_loop(self, folder: str, mark_as_read: bool):\n        \"\"\"Main polling loop (runs in separate thread)\"\"\"\n        while self._polling_active:\n            try:\n                emails = self.receive(\n                    callback=self._email_callback,\n                    folder=folder,\n                    mark_as_read=mark_as_read,\n                )\n                self.logger.debug(\n                    \"Polling check completed, found %d new emails\", len(emails)\n                )\n\n            except Exception as err:\n                self.logger.error(\"Error during email polling: %s\", err)\n\n            # Sleep in small increments to allow for responsive shutdown\n            for _ in range(self.polling_timer):\n                if not self._polling_active:\n                    break\n                time.sleep(1)\n\n    def _email_parse(self, email_message) -> Dict[str, Any]:\n        \"\"\"Parse email message into dictionary\"\"\"\n        self.logger.debug(\"EmailHandler._email_parse()\")\n\n        parsed = {\n            \"subject\": email_message.get(\"Subject\", \"\"),\n            \"from\": email_message.get(\"From\", \"\"),\n            \"to\": email_message.get(\"To\", \"\"),\n            \"date\": email_message.get(\"Date\", \"\"),\n            \"body\": \"\",\n            \"html_body\": \"\",\n            \"attachments\": [],\n        }\n\n        # Extract body content\n        if email_message.is_multipart():\n            for part in email_message.walk():\n                content_type = part.get_content_type()\n                content_disposition = str(part.get(\"Content-Disposition\", \"\"))\n\n                if (\n                    content_type == \"text/plain\"\n                    and \"attachment\" not in content_disposition\n                ):\n                    parsed[\"body\"] = part.get_payload(decode=True).decode(\n                        \"utf-8\", errors=\"ignore\"\n                    )\n                elif (\n                    content_type == \"text/html\"\n                    and \"attachment\" not in content_disposition\n                ):\n                    parsed[\"html_body\"] = part.get_payload(decode=True).decode(\n                        \"utf-8\", errors=\"ignore\"\n                    )\n                elif \"attachment\" in content_disposition:\n                    filename = part.get_filename()\n                    if filename:\n                        parsed[\"attachments\"].append(\n                            {\n                                \"filename\": filename,\n                                \"content_type\": content_type,\n                                \"content\": part.get_payload(decode=True),\n                            }\n                        )\n        else:\n            parsed[\"body\"] = email_message.get_payload(decode=True).decode(\n                \"utf-8\", errors=\"ignore\"\n            )\n\n        self.logger.debug(\"EmailHandler._email_parse() ended\")\n        return parsed\n\n    def _smtp_config_validate(self) -> bool:\n        \"\"\"Validate SMTP configuration\"\"\"\n        if not self.smtp_server:\n            self.logger.error(\"SMTP server not configured\")\n            return False\n        if not self.email_address:\n            self.logger.error(\"Email address not configured\")\n            return False\n        if not self.username or not self.password:\n            self.logger.error(\"Username or password not configured\")\n            return False\n        return True\n\n    def _imap_config_validate(self) -> bool:\n        \"\"\"Validate IMAP configuration\"\"\"\n        if not self.imap_server:\n            self.logger.error(\"IMAP server not configured\")\n            return False\n        if not self.username or not self.password:\n            self.logger.error(\"Username or password not configured\")\n            return False\n        return True\n"
  },
  {
    "path": "acme_srv/error.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Error class\"\"\"\n# pylint: disable=c0209\nfrom __future__ import print_function\n\n\nclass Error(object):\n    \"\"\"error messages\"\"\"\n\n    def __init__(self, debug=None, logger=None):\n        self.debug = debug\n        self.logger = logger\n\n    def _acme_errormessage(self, message):\n        \"\"\"dictionary containing the implemented acme error messages\"\"\"\n        self.logger.debug(\"Error.acme_errormessage({0})\".format(message))\n        error_dic = {\n            \"urn:ietf:params:acme:error:accountDoesNotExist\": None,\n            \"urn:ietf:params:acme:error:badCSR\": None,\n            \"urn:ietf:params:acme:error:badNonce\": \"JWS has invalid anti-replay nonce\",\n            \"urn:ietf:params:acme:error:invalidContact\": \"The provided contact URI was invalid\",\n            \"urn:ietf:params:acme:error:malformed\": None,\n            \"urn:ietf:params:acme:error:serverInternal\": None,\n            \"urn:ietf:params:acme:error:unauthorized\": None,\n            \"urn:ietf:params:acme:error:userActionRequired\": None,\n            \"urn:ietf:params:acme:error:alreadyRevoked\": None,\n            \"notImplementedYet\": \"we are not that far. Stay tuned\",\n        }\n        if message and message in error_dic:\n            result = error_dic[message]\n        else:\n            result = None\n        return result\n\n    def enrich_error(self, message, detail=None):\n        \"\"\"put some more content into the error messgae\"\"\"\n        self.logger.debug(\"Error.enrich_error()\")\n        error_message = self._acme_errormessage(message)\n\n        if message and error_message:\n            detail = \"{0}: {1}\".format(error_message, detail)\n        elif error_message:\n            detail = \"{0}{1}\".format(error_message, detail)\n\n        return detail\n"
  },
  {
    "path": "acme_srv/helper.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=w0611\n\"\"\"\nBackwards compatibility layer for acme2certifier helper functions.\nThis file imports all functions from the modular helpers structure\nto maintain compatibility with existing code.\n\"\"\"\n\n# Encoding and base64 operations\nfrom .helpers.encoding import (\n    b64decode_pad,\n    b64_decode,\n    b64_encode,\n    b64_url_encode,\n    b64_url_recode,\n    b64_url_decode,\n    build_pem_file,\n    convert_byte_to_string,\n    convert_string_to_byte,\n)\n\n# Certificate operations\nfrom .helpers.certificates import (\n    cert_aki_get,\n    cert_aki_pyopenssl_get,\n    cert_load,\n    cert_dates_get,\n    cert_cn_get,\n    cert_der2pem,\n    cert_issuer_get,\n    cert_pem2der,\n    cert_pubkey_get,\n    cert_san_pyopenssl_get,\n    cert_san_get,\n    cert_ski_pyopenssl_get,\n    cert_ski_get,\n    cryptography_version_get,\n    cert_extensions_get,\n    cert_extensions_py_openssl_get,\n    cert_serial_get,\n    pembundle_to_list,\n    certid_asn1_get,\n    certid_hex_get,\n    certid_check,\n)\n\n# CSR operations\nfrom .helpers.csr import (\n    csr_load,\n    csr_cn_get,\n    csr_dn_get,\n    csr_pubkey_get,\n    csr_san_get,\n    csr_san_byte_get,\n    csr_extensions_get,\n    csr_subject_get,\n    csr_cn_lookup,\n)\n\n# Cryptographic operations\nfrom .helpers.crypto import (\n    decode_deserialize,\n    decode_message,\n    generate_random_string,\n    jwk_thumbprint_get,\n    sha256_hash,\n    sha256_hash_hex,\n    signature_check,\n    string_sanitize,\n)\n\n# Date/time utilities\nfrom .helpers.datetime_utils import (\n    uts_now,\n    uts_to_date_utc,\n    date_to_uts_utc,\n    date_to_datestr,\n    datestr_to_date,\n)\n\n# Validation functions\nfrom .helpers.validation import (\n    dkeys_lower,\n    fqdn_in_san_check,\n    validate_csr,\n    validate_email,\n    validate_identifier,\n    validate_ip,\n    validate_fqdn,\n    ip_validate,\n    ipv6_chk,\n    cn_validate,\n)\n\n# Network operations\nfrom .helpers.network import (\n    _fqdn_resolve,\n    fqdn_resolve,\n    ptr_resolve,\n    dns_server_list_load,\n    patched_create_connection,\n    proxy_check,\n    url_get_with_own_dns,\n    allowed_gai_family,\n    url_get_with_default_dns,\n    url_get,\n    txt_get,\n    proxystring_convert,\n    servercert_get,\n    v6_adjust,\n    header_info_get,\n    get_url,\n    parse_url,\n    encode_url,\n    request_operation,\n)\n\n# Configuration\nfrom .helpers.config import (\n    config_check,\n    config_profile_load,\n    config_eab_profile_load,\n    config_headerinfo_load,\n    config_enroll_config_log_load,\n    config_allowed_domainlist_load,\n    config_async_mode_load,\n    config_proxy_load,\n    load_config,\n    header_info_jsonify,\n    header_info_lookup,\n    client_parameter_validate,\n    profile_lookup,\n)\n\n# Logging utilities\nfrom .helpers.logging_utils import (\n    _logger_nonce_modify,\n    _logger_certificate_modify,\n    _logger_token_modify,\n    _logger_challenges_modify,\n    logger_info,\n    logger_setup,\n    print_debug,\n    handle_exception,\n)\n\n# Plugin loaders\nfrom .helpers.plugin_loader import ca_handler_load, eab_handler_load, hooks_load\n\n# EAB functions\nfrom .helpers.eab import (\n    eab_profile_header_info_check,\n    eab_profile_subject_string_check,\n    eab_profile_subject_check,\n    eab_profile_revocation_check,\n    eab_profile_check,\n    eab_profile_list_check,\n    eab_profile_string_check,\n)\n\n# Domain utilities\nfrom .helpers.domain_utils import (\n    encode_domain,\n    wildcard_domain_check,\n    pattern_check,\n    is_domain_whitelisted,\n    allowed_domainlist_check,\n    sancheck_lists_create,\n)\n\n# General utilities\nfrom .helpers.utils import (\n    error_dic_get,\n    enrollment_config_log,\n    radomize_parameter_list,\n    handler_config_check,\n)\n"
  },
  {
    "path": "acme_srv/helpers/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Helper modules for acme2certifier\"\"\"\n"
  },
  {
    "path": "acme_srv/helpers/certificates.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Certificate utilities for acme2certifier\"\"\"\nimport base64\nimport logging\nfrom typing import List, Tuple\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization, hashes\nfrom cryptography.x509 import load_pem_x509_certificate, ocsp\nfrom OpenSSL import crypto\nfrom .encoding import (\n    convert_string_to_byte,\n    convert_byte_to_string,\n    build_pem_file,\n    b64_url_recode,\n    b64_decode,\n)\nfrom .datetime_utils import date_to_uts_utc\n\n\ndef cert_aki_get(logger: logging.Logger, certificate: str) -> str:\n    \"\"\"get subject key identifier from certificate\"\"\"\n    logger.debug(\"Helper.cert_ski_get()\")\n\n    cert = cert_load(logger, certificate, recode=True)\n    try:\n        aki = cert.extensions.get_extension_for_oid(x509.OID_AUTHORITY_KEY_IDENTIFIER)\n        aki_value = aki.value.key_identifier.hex()\n    except Exception as _err:\n        aki_value = cert_aki_pyopenssl_get(logger, certificate)\n    logger.debug(\"cert_aki_get() ended with: %s\", aki_value)\n    return aki_value\n\n\ndef cert_aki_pyopenssl_get(logger, certificate: str) -> str:\n    \"\"\"Get Authority Key Identifier from a certificate as a hex string.\"\"\"\n    logger.debug(\"Helper.cert_aki_pyopenssl_cert()\")\n\n    pem_data = convert_string_to_byte(\n        build_pem_file(logger, None, b64_url_recode(logger, certificate), True)\n    )\n    cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_data)\n    # Get the AKI extension\n    aki = None\n    for i in range(cert.get_extension_count()):\n        ext = cert.get_extension(i)\n        if \"authorityKeyIdentifier\" in str(ext.get_short_name()):\n            aki = ext\n    if aki:\n        # Get the SKI value and convert it to hex\n        aki_hex = aki.get_data()[4:].hex()\n    else:\n        logger.warning(\"No AKI found in certificate\")\n        aki_hex = None\n    logger.debug(\"Helper.cert_ski_pyopenssl_cert() ended with: %s\", aki_hex)\n    return aki_hex\n\n\ndef cert_load(\n    logger: logging.Logger, certificate: str, recode: bool\n) -> x509.Certificate:\n    \"\"\"load certificate object from pem _Format\"\"\"\n    logger.debug(\"Helper.cert_load(%s)\", recode)\n\n    if recode:\n        pem_data = convert_string_to_byte(\n            build_pem_file(logger, None, b64_url_recode(logger, certificate), True)\n        )\n    else:\n        pem_data = convert_string_to_byte(certificate)\n    cert = x509.load_pem_x509_certificate(pem_data, default_backend())\n\n    return cert\n\n\ndef cert_dates_get(logger: logging.Logger, certificate: str) -> Tuple[int, int]:\n    \"\"\"get date number form certificate\"\"\"\n    logger.debug(\"Helper.cert_dates_get()\")\n\n    issue_date = 0\n    expiration_date = 0\n    try:\n        cert = cert_load(logger, certificate, recode=True)\n        issue_date = date_to_uts_utc(\n            cert.not_valid_before_utc, _tformat=\"%Y-%m-%d %H:%M:%S\"\n        )\n        expiration_date = date_to_uts_utc(\n            cert.not_valid_after_utc, _tformat=\"%Y-%m-%d %H:%M:%S\"\n        )\n    except Exception as err:\n        logger.debug(\n            \"Error while getting dates from certificate. Fallback to deprecated method: %s\",\n            err,\n        )\n        try:\n            issue_date = date_to_uts_utc(\n                cert.not_valid_before, _tformat=\"%Y-%m-%d %H:%M:%S\"\n            )\n            expiration_date = date_to_uts_utc(\n                cert.not_valid_after, _tformat=\"%Y-%m-%d %H:%M:%S\"\n            )\n        except Exception:\n            logger.error(\"Error while getting dates from certificate: %s\", err)\n            issue_date = 0\n            expiration_date = 0\n\n    logger.debug(\"cert_dates_get() ended with: %s/%s\", issue_date, expiration_date)\n    return (issue_date, expiration_date)\n\n\ndef cert_cn_get(logger: logging.Logger, certificate: str) -> str:\n    \"\"\"get cn from certificate\"\"\"\n    logger.debug(\"Helper.cert_cn_get()\")\n\n    cert = cert_load(logger, certificate, recode=True)\n    # get subject and look for common name\n    subject = cert.subject\n    result = None\n    for attr in subject:\n        if attr.oid == x509.NameOID.COMMON_NAME:\n            result = attr.value\n            break\n    logger.debug(\"Helper.cert_cn_get() ended with: %s\", result)\n    return result\n\n\ndef cert_der2pem(der_cert: bytes) -> str:\n    \"\"\"convert certificate der to pem\"\"\"\n    cert = x509.load_der_x509_certificate(der_cert)\n    pem_cert = cert.public_bytes(serialization.Encoding.PEM)\n    return pem_cert\n\n\ndef cert_issuer_get(logger: logging.Logger, certificate: str) -> str:\n    \"\"\"get certificate issuer from certificate\"\"\"\n    logger.debug(\"Helper.cert_issuer_get()\")\n\n    cert = cert_load(logger, certificate, recode=True)\n    result = cert.issuer.rfc4514_string()\n    logger.debug(\"Helper.cert_issuer_get() ended with: %s\", result)\n    return result\n\n\ndef cert_pem2der(pem_cert: str) -> bytes:\n    \"\"\"convert certificate pem to der\"\"\"\n    cert = x509.load_pem_x509_certificate(pem_cert.encode(), default_backend())\n    der_cert = cert.public_bytes(serialization.Encoding.DER)\n    return der_cert\n\n\ndef cert_pubkey_get(logger: logging.Logger, certificate=str) -> str:\n    \"\"\"get public key from certificate\"\"\"\n    logger.debug(\"Helper.cert_pubkey_get()\")\n    cert = cert_load(logger, certificate, recode=False)\n    public_key = cert.public_key()\n    pubkey_str = public_key.public_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PublicFormat.SubjectPublicKeyInfo,\n    )\n    logger.debug(\"Helper.cert_pubkey_get() ended with: %s\", pubkey_str)\n    return convert_byte_to_string(pubkey_str)\n\n\ndef cert_san_pyopenssl_get(logger, certificate, recode=True):\n    \"\"\"get subject alternate names from certificate\"\"\"\n    logger.debug(\"Helper.cert_san_pyopenssl_get()\")\n    if recode:\n        pem_file = build_pem_file(\n            logger, None, b64_url_recode(logger, certificate), True\n        )\n    else:\n        pem_file = certificate\n\n    cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_file)\n    san = []\n    ext_count = cert.get_extension_count()\n    for i in range(0, ext_count):\n        ext = cert.get_extension(i)\n        if \"subjectAltName\" in str(ext.get_short_name()):\n            # pylint: disable=c2801\n            san_list = ext.__str__().split(\",\")\n            for san_name in san_list:\n                san_name = san_name.rstrip()\n                san_name = san_name.lstrip()\n                san.append(san_name)\n\n    logger.debug(\"Helper.cert_san_pyopenssl_get() ended\")\n    return san\n\n\ndef cert_san_get(\n    logger: logging.Logger, certificate: str, recode: bool = True\n) -> List[str]:\n    \"\"\"get subject alternate names from certificate\"\"\"\n    logger.debug(\"Helper.cert_san_get(%s)\", recode)\n\n    cert = cert_load(logger, certificate, recode=recode)\n    sans = []\n    try:\n        ext = cert.extensions.get_extension_for_oid(x509.OID_SUBJECT_ALTERNATIVE_NAME)\n        sans_list = ext.value.get_values_for_type(x509.DNSName)\n        for san in sans_list:\n            sans.append(f\"DNS:{san}\")\n        sans_list = ext.value.get_values_for_type(x509.IPAddress)\n        for san in sans_list:\n            sans.append(f\"IP:{san}\")\n    except Exception as err:\n        logger.error(\"Error while getting SANs from certificate: %s\", err)\n        # fallback to pyopenssl method if there is an error (e.g. SAN extension not found)\n        # sans = cert_san_pyopenssl_get(logger, certificate, recode=recode)\n\n    logger.debug(\"Helper.cert_san_get() ended\")\n    return sans\n\n\ndef cert_ski_pyopenssl_get(logger, certificate: str) -> str:\n    \"\"\"Get Subject Key Identifier from a certificate as a hex string.\"\"\"\n    logger.debug(\"Helper.cert_ski_pyopenssl_cert()\")\n\n    pem_data = convert_string_to_byte(\n        build_pem_file(logger, None, b64_url_recode(logger, certificate), True)\n    )\n    cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_data)\n    # Get the SKI extension\n    ski = None\n    for i in range(cert.get_extension_count()):\n        ext = cert.get_extension(i)\n        if \"subjectKeyIdentifier\" in str(ext.get_short_name()):\n            ski = ext\n    if ski:\n        # Get the SKI value and convert it to hex\n        ski_hex = ski.get_data()[2:].hex()\n    else:\n        logger.warning(\"No SKI found in certificate\")\n        ski_hex = None\n    logger.debug(\"Helper.cert_ski_pyopenssl_cert() ended with: %s\", ski_hex)\n    return ski_hex\n\n\ndef cert_ski_get(logger: logging.Logger, certificate: str) -> str:\n    \"\"\"get subject key identifier from certificate\"\"\"\n    logger.debug(\"Helper.cert_ski_get()\")\n\n    cert = cert_load(logger, certificate, recode=True)\n    try:\n        ski = cert.extensions.get_extension_for_oid(x509.OID_SUBJECT_KEY_IDENTIFIER)\n        ski_value = ski.value.digest.hex()\n    except Exception as err:\n        logger.error(\"Error while getting the SKI fallback to Openssl method: %s\", err)\n        ski_value = cert_ski_pyopenssl_get(logger, certificate)\n    logger.debug(\"Helper.cert_ski_get() ended with: %s\", ski_value)\n    return ski_value\n\n\ndef cryptography_version_get(logger: logging.Logger) -> int:\n    \"\"\"get version number of cryptography module\"\"\"\n    logger.debug(\"Helper.cryptography_version_get()\")\n    # pylint: disable=c0415\n    import cryptography\n\n    major_version = None\n    try:\n        version_list = cryptography.__version__.split(\".\")\n        if version_list:\n            major_version = int(version_list[0])\n    except Exception as err:\n        logger.error(\n            \"Error while getting the version number of the cryptography module: %s\", err\n        )\n        major_version = 36\n\n    logger.debug(\"cryptography_version_get() ended with %s\", major_version)\n    return major_version\n\n\ndef cert_extensions_get(logger: logging.Logger, certificate: str, recode: bool = True):\n    \"\"\"get extenstions from certificate certificate\"\"\"\n    logger.debug(\"Helper.cert_extensions_get()\")\n\n    crypto_module_version = cryptography_version_get(logger)\n    if crypto_module_version < 36:\n        logger.debug(\"Helper.cert_extensions_get(): using pyopenssl\")\n        extension_list = cert_extensions_py_openssl_get(logger, certificate, recode)\n    else:\n        cert = cert_load(logger, certificate, recode=recode)\n        extension_list = []\n        for extension in cert.extensions:\n            extension_list.append(\n                convert_byte_to_string(base64.b64encode(extension.value.public_bytes()))\n            )\n\n    logger.debug(\"Helper.cert_extensions_get() ended with: %s\", extension_list)\n    return extension_list\n\n\ndef cert_extensions_py_openssl_get(logger, certificate, recode=True):\n    \"\"\"get extenstions from certificate certificate\"\"\"\n    logger.debug(\"cert_extensions_py_openssl_get()\")\n    if recode:\n        pem_file = build_pem_file(\n            logger, None, b64_url_recode(logger, certificate), True\n        )\n    else:\n        pem_file = certificate\n\n    cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_file)\n    extension_list = []\n    ext_count = cert.get_extension_count()\n    for i in range(0, ext_count):\n        ext = cert.get_extension(i)\n        extension_list.append(convert_byte_to_string(base64.b64encode(ext.get_data())))\n\n    logger.debug(\"cert_extensions_py_openssl_get() ended with: %s\", extension_list)\n    return extension_list\n\n\ndef cert_serial_get(logger: logging.Logger, certificate: str, hexformat: bool = False):\n    \"\"\"get serial number form certificate\"\"\"\n    logger.debug(\"Helper.cert_serial_get()\")\n    cert = cert_load(logger, certificate, recode=True)\n    if hexformat:\n        serial_number = f\"{cert.serial_number:x}\"\n        # add leading zero if needed\n        serial_number = serial_number.zfill(len(serial_number) + len(serial_number) % 2)\n    else:\n        serial_number = cert.serial_number\n    logger.debug(\"Helper.cert_serial_get() ended with: %s\", serial_number)\n    return serial_number\n\n\ndef pembundle_to_list(logger: logging.Logger, pem_bundle: str) -> List[str]:\n    \"\"\"split pem bundle into a list of certificates\"\"\"\n    logger.debug(\"Helper.pembundle_to_list()\")\n    cert_list = []\n    pem_data = \"\"\n    if \"-----BEGIN CERTIFICATE-----\" in pem_bundle:\n        for line in pem_bundle.splitlines():\n            line = line.strip()\n            if line.startswith(\"-----BEGIN CERTIFICATE-----\") and pem_data:\n                cert_list.append(pem_data)\n                pem_data = \"\"\n            pem_data += line + \"\\n\"\n        if pem_data:\n            cert_list.append(pem_data)\n    logger.debug(\"Helper.pembundle_to_list() returned %s certificates\", cert_list)\n    return cert_list\n\n\ndef certid_asn1_get(logger: logging.Logger, cert_pem: str, issuer_pem: str) -> str:\n    \"\"\"get renewal information from certificate\"\"\"\n    logger.debug(\"Helper.certid_asn1_get()\")\n\n    cert = load_pem_x509_certificate(convert_string_to_byte(cert_pem))\n    issuer = load_pem_x509_certificate(convert_string_to_byte(issuer_pem))\n\n    builder = ocsp.OCSPRequestBuilder()\n    builder = builder.add_certificate(cert, issuer, hashes.SHA256())\n    ocsprequest = builder.build()\n    ocsprequest_hex = ocsprequest.public_bytes(serialization.Encoding.DER).hex()\n\n    # this is ugly but i did not find a better way to do this\n    _header, certid_hex = ocsprequest_hex.split(\"0420\", 1)\n\n    return certid_hex\n\n\ndef certid_hex_get(logger: logging.Logger, renewal_info: str) -> Tuple[str, str]:\n    \"\"\"get certid in hex from renewal_info field\"\"\"\n    logger.debug(\"Helper.certid_hex_get()\")\n\n    renewal_info_b64 = b64_url_recode(logger, renewal_info)\n    renewal_info_hex = b64_decode(logger, renewal_info_b64).hex()\n\n    # this is ugly but i did not find a better way to do this\n    mda, certid_renewal = renewal_info_hex.split(\"0420\", 1)\n    mda = mda[4:]\n\n    logger.debug(\"Helper.certid_hex_get() endet with %s\", certid_renewal)\n    return mda, certid_renewal\n\n\ndef certid_check(\n    logger: logging.Logger, renewal_info: str, certid_database: str\n) -> str:\n    \"\"\"compare certid with renewal info\"\"\"\n    logger.debug(\"Helper.certid_check()\")\n\n    renewal_info_b64 = b64_url_recode(logger, renewal_info)\n    renewal_info_hex = b64_decode(logger, renewal_info_b64).hex()\n\n    # this is ugly but i did not find a better way to do this\n    _header, certid_renewal = renewal_info_hex.split(\"0420\", 1)\n    result = certid_renewal == certid_database\n\n    logger.debug(\"Helper.certid_check() ended with: %s\", result)\n    return result\n"
  },
  {
    "path": "acme_srv/helpers/config.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Configuration utilities for acme2certifier\"\"\"\nimport configparser\nimport json\nimport logging\nimport os\nfrom typing import Dict, List, Tuple\nfrom .plugin_loader import eab_handler_load\nfrom .global_variables import PARSING_ERR_MSG\n\n\ndef config_check(logger: logging.Logger, config_dic: Dict):\n    \"\"\"check configuration\"\"\"\n    logger.debug(\"Helper.config_check()\")\n\n    for section, section_dic in config_dic.items():\n        for key, value in section_dic.items():\n            if value.startswith('\"') or value.endswith('\"'):\n                logger.warning(\n                    'Section %s option: %s contains \" characters. Please check if this is required!',\n                    section,\n                    key,\n                )\n\n\ndef config_profile_load(logger: logging.Logger, config_dic: Dict[str, str]):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_profile_load()\")\n\n    # load profiles\n    profiles = {}\n    if \"Order\" in config_dic and \"profiles\" in config_dic[\"Order\"]:\n        try:\n            profiles = json.loads(config_dic[\"Order\"][\"profiles\"])\n        except Exception as err_:\n            logger.warning(\"Failed to load profiles from configuration: %s\", err_)\n\n    logger.debug(\"Helper.config_profile_load() ended\")\n    return profiles\n\n\ndef config_eab_profile_load(logger: logging.Logger, config_dic: Dict[str, str]):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_eab_profile_load()\")\n\n    eab_profiling = False\n    eab_handler = None\n\n    try:\n        # load eab_profiling from eabhandler section\n        eab_profiling = config_dic.getboolean(\n            \"EABhandler\", \"eab_profiling\", fallback=False\n        )\n    except Exception as err:\n        logger.error(\"Failed to load eabprofile from configuration: %s\", err)\n        eab_profiling = False\n\n    if (\n        not eab_profiling\n        and \"CAhandler\" in config_dic\n        and \"eab_profiling\" in config_dic[\"CAhandler\"]\n    ):\n        # load eab_profiling from CAHandler section - deprecated\n        logger.warning(\n            \"eab_profiling found in CAhandler section - this is deprecated, please use EABhandler section\"\n        )\n        try:\n            eab_profiling = config_dic.getboolean(\n                \"CAhandler\", \"eab_profiling\", fallback=False\n            )\n        except Exception as err:\n            logger.error(\"Failed to load eabprofile from configuration: %s\", err)\n            eab_profiling = False\n\n    if eab_profiling:\n        if (\n            \"EABhandler\" in config_dic\n            and \"eab_handler_file\" in config_dic[\"EABhandler\"]\n        ):\n            # load eab_handler according to configuration\n            eab_handler_module = eab_handler_load(logger, config_dic)\n            if not eab_handler_module:\n                logger.critical(\"EABHandler could not get loaded\")\n            else:\n                eab_handler = eab_handler_module.EABhandler\n        else:\n            logger.critical(\"EABHandler configuration incomplete\")\n\n    logger.debug(\"_config_profile_load() ended\")\n    return eab_profiling, eab_handler\n\n\ndef config_headerinfo_load(logger: logging.Logger, config_dic: Dict[str, str]):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_headerinfo_load()\")\n\n    header_info_field = None\n    if (\n        \"Order\" in config_dic\n        and \"header_info_list\" in config_dic[\"Order\"]\n        and config_dic[\"Order\"][\"header_info_list\"]\n    ):\n        try:\n            header_info_field = json.loads(config_dic[\"Order\"][\"header_info_list\"])[0]\n        except Exception as err_:\n            logger.warning(\n                \"Failed to parse header_info_list from configuration: %s\", err_\n            )\n    #\n    logger.debug(\"Helper.config_headerinfo_load() ended\")\n    return header_info_field\n\n\ndef config_enroll_config_log_load(logger: logging.Logger, config_dic: Dict[str, str]):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_enroll_config_log_load()\")\n\n    enrollment_cfg_log = False\n    enrollment_cfg_log_skip_list = []\n\n    if \"CAhandler\" in config_dic:\n        try:\n            enrollment_cfg_log = config_dic.getboolean(\n                \"CAhandler\", \"enrollment_config_log\", fallback=False\n            )\n        except Exception as err_:\n            logger.warning(\n                \"Failed to load enrollment_config_log from configuration: %s\", err_\n            )\n\n        if \"enrollment_config_log_skip_list\" in config_dic[\"CAhandler\"]:\n            try:\n                enrollment_cfg_log_skip_list = json.loads(\n                    config_dic[\"CAhandler\"][\"enrollment_config_log_skip_list\"]\n                )\n            except Exception as err_:\n                logger.warning(\n                    \"Failed to parse enrollment_config_log_skip_list from configuration: %s\",\n                    err_,\n                )\n                enrollment_cfg_log_skip_list = PARSING_ERR_MSG\n\n    logger.debug(\n        \"Helper.config_enroll_config_log_load() ended with: %s\", enrollment_cfg_log\n    )\n    return enrollment_cfg_log, enrollment_cfg_log_skip_list\n\n\ndef config_allowed_domainlist_load(logger: logging.Logger, config_dic: Dict[str, str]):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_allowed_domainlist_load()\")\n\n    allowed_domainlist = []\n\n    if \"Order\" in config_dic and \"allowed_domainlist\" in config_dic[\"Order\"]:\n        try:\n            allowed_domainlist = json.loads(config_dic[\"Order\"][\"allowed_domainlist\"])\n        except Exception as err_:\n            logger.warning(\n                \"Failed to load allowed_domainlist from configuration: %s\", err_\n            )\n            allowed_domainlist = PARSING_ERR_MSG\n\n    if (\n        not allowed_domainlist\n        and \"CAhandler\" in config_dic\n        and \"allowed_domainlist\" in config_dic[\"CAhandler\"]\n    ):\n        logger.warning(\n            \"allowed_domainlist parameter found in CAhandler section - this is deprecated, please use Order section\"\n        )\n        try:\n            allowed_domainlist = json.loads(\n                config_dic[\"CAhandler\"][\"allowed_domainlist\"]\n            )\n        except Exception as err_:\n            logger.warning(\n                \"Failed to load allowed_domainlist from configuration: %s\", err_\n            )\n            allowed_domainlist = PARSING_ERR_MSG\n\n    logger.debug(\n        \"Helper.config_allowed_domainlist_load() ended with: %s\", allowed_domainlist\n    )\n    return allowed_domainlist\n\n\ndef config_async_mode_load(\n    logger: logging.Logger, config_dic: Dict[str, str], db_type: str\n):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"Helper.config_async_mode_load()\")\n\n    async_mode = False\n\n    async_cfg = config_dic.getboolean(\"DEFAULT\", \"async_mode\", fallback=False)\n    if async_cfg:\n        if db_type == \"django\":\n            async_mode = True\n        else:\n            logger.info(\n                \"asynchronous Challenge validation disabled, requires django db handler\"\n            )\n    logger.debug(\"Helper.config_async_mode_load() ended with: %s\", async_mode)\n    return async_mode\n\n\ndef config_proxy_load(logger, config_dic: Dict[str, str], host_name: str):\n    \"\"\"load parameters\"\"\"\n    logger.debug(\"_config_proxy_load()\")\n\n    # Lazy import to avoid circular dependency\n    from .network import parse_url, proxy_check  # pylint: disable=C0415\n\n    proxy = {}\n    if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n        try:\n            proxy_list = json.loads(config_dic[\"DEFAULT\"][\"proxy_server_list\"])\n            url_dic = parse_url(logger, host_name)\n            if \"host\" in url_dic:\n                # check if we need to set the proxy\n                (fqdn, _port) = url_dic[\"host\"].split(\":\")\n                proxy_server = proxy_check(logger, fqdn, proxy_list)\n                proxy = {\"http\": proxy_server, \"https\": proxy_server}\n        except Exception as err_:\n            logger.warning(\n                \"Failed to parse proxy_server_list from configuration: %s\",\n                err_,\n            )\n\n    logger.debug(\"config_proxy_load() ended with: %s\", proxy)\n    return proxy\n\n\ndef load_config(\n    logger: logging.Logger = None, mfilter: str = None, cfg_file: str = None\n) -> configparser.ConfigParser:\n    \"\"\"small configparser wrappter to load a config file\"\"\"\n    if not cfg_file:\n        if \"ACME_SRV_CONFIGFILE\" in os.environ:\n            cfg_file = os.environ[\"ACME_SRV_CONFIGFILE\"]\n        else:\n            # go up one directory from helpers/ to acme_srv/ to find config file\n            cfg_file = os.path.dirname(os.path.dirname(__file__)) + \"/\" + \"acme_srv.cfg\"\n    if logger:\n        logger.debug(\"load_config(%s:%s)\", mfilter, cfg_file)\n    config = configparser.ConfigParser(interpolation=None)\n    config.optionxform = str\n    config.read(cfg_file, encoding=\"utf8\")\n    return config\n\n\ndef header_info_jsonify(logger: logging.Logger, header_info: str) -> Dict[str, str]:\n    \"\"\"jsonify header info\"\"\"\n    logger.debug(\"Helper.header_info_json_parse()\")\n\n    header_info_dic = {}\n    try:\n        if isinstance(header_info, list) and \"header_info\" in header_info[-1]:\n            header_info_dic = json.loads(header_info[-1][\"header_info\"])\n    except Exception as err:\n        logger.error(\"Could not parse header_info_field: %s\", err)\n\n    logger.debug(\n        \"Helper.header_info_json_parse() ended with: %s\", bool(header_info_dic)\n    )\n    return header_info_dic\n\n\ndef header_info_lookup(logger, csr: str, header_info_field, key: str) -> str:\n    \"\"\"lookup header info\"\"\"\n    logger.debug(\"Helper.header_info_lookup(%s)\", key)\n\n    # Lazy import to avoid circular dependency\n    from .network import header_info_get  # pylint: disable=C0415\n\n    result = None\n    header_info = header_info_get(logger, csr=csr)\n\n    if header_info:\n        header_info_dic = header_info_jsonify(logger, header_info)\n        if header_info_field in header_info_dic:\n            for ele in header_info_dic[header_info_field].split(\" \"):\n                if key in ele.lower():\n                    result = ele.split(\"=\", 1)[1]\n                    break\n        else:\n            logger.warning(\n                \"Header_info_field not found in header info: %s\", header_info_field\n            )\n    logger.debug(\"Helper.header_info_lookup(%s) ended with: %s\", key, result)\n    return result\n\n\ndef profile_lookup(logger: logging.Logger, csr: str) -> str:\n    \"\"\"get profile name from csr\"\"\"\n    logger.debug(\"Helper.profile_lookup()\")\n\n    from acme_srv.db_handler import DBstore  # pylint: disable=c0415\n\n    dbstore = DBstore(logger=logger)\n\n    try:\n        result = dbstore.certificates_search(\n            \"csr\", csr, [\"id\", \"order_id\", \"order__profile\"]\n        )\n    except Exception as err:\n        logger.warning(\"Profile lookup failed with: %s\", err)\n        result = None\n    if result and \"order__profile\" in result[0]:\n        # we have a match - get profile name\n        profile_name = result[0][\"order__profile\"]\n    else:\n        profile_name = None\n\n    logger.debug(\"Helper.profile_lookup() ended with: %s\", profile_name)\n    return profile_name\n\n\ndef client_parameter_validate(\n    logger, csr: str, cahandler, value: str, value_list: List[str]\n) -> Tuple[str, str]:\n    \"\"\"select value from list\"\"\"\n    logger.debug(\"Helper.client_parameter_validate(%s)\", value)\n\n    value_to_set = None\n    error = None\n    if cahandler.profiles:\n        logger.debug(\"Helper.client_parameter_validate(): using profile\")\n        # get profile info\n        client_parameter = profile_lookup(logger, csr)\n    else:\n        logger.debug(\"Helper.client_parameter_validate(): using header info\")\n        # get header info\n        client_parameter = header_info_lookup(\n            logger, csr, cahandler.header_info_field, value\n        )\n    if client_parameter:\n        if client_parameter in value_list:\n            value_to_set = client_parameter\n        else:\n            error = f'{value} \"{client_parameter}\" is not allowed'\n    else:\n        # header not set, use first value from list\n        value_to_set = value_list[0]\n\n    logger.debug(\n        \"Helper.client_parameter_validate(%s) ended with %s/%s\",\n        value,\n        value_to_set,\n        error,\n    )\n    return value_to_set, error\n"
  },
  {
    "path": "acme_srv/helpers/crypto.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Cryptographic operations for acme2certifier\"\"\"\nimport hashlib\nimport json\nimport logging\nimport random\nimport re\nfrom string import digits, ascii_letters\nfrom typing import Dict, Tuple\nfrom jwcrypto import jwk, jws\nfrom .encoding import b64decode_pad, b64_encode\nfrom .validation import dkeys_lower\n\n\ndef decode_deserialize(logger: logging.Logger, string: str) -> Dict:\n    \"\"\"decode and deserialize string\"\"\"\n    logger.debug(\"Helper.decode_deserialize()\")\n    # b64 decode\n    string_decode = b64decode_pad(logger, string)\n    # deserialize if b64 decoding was successful\n    if string_decode and string_decode != \"ERR: b64 decoding error\":\n        try:\n            string_decode = json.loads(string_decode)\n        except ValueError:\n            string_decode = \"ERR: Json decoding error\"\n\n    return string_decode\n\n\ndef decode_message(\n    logger: logging.Logger, message: str\n) -> Tuple[str, str, Dict[str, str], Dict[str, str], str]:\n    \"\"\"decode jwstoken and return header, payload and signature\"\"\"\n    logger.debug(\"Helper.decode_message()\")\n    jwstoken = jws.JWS()\n    result = False\n    error = None\n    try:\n        jwstoken.deserialize(message)\n        protected = json.loads(jwstoken.objects[\"protected\"])\n        if bool(jwstoken.objects[\"payload\"]):\n            payload = json.loads(jwstoken.objects[\"payload\"])\n        else:\n            payload = {}\n        signature = jwstoken.objects[\"signature\"]\n        result = True\n    except Exception as err:\n        logger.error(\"Error during message decoding %s\", err)\n        error = str(err)\n        protected = {}\n        payload = {}\n        signature = None\n\n    if payload:\n        payload = dkeys_lower(payload)\n    return (result, error, protected, payload, signature)\n\n\ndef generate_random_string(logger: logging.Logger, length: int) -> str:\n    \"\"\"generate random string to be used as name\"\"\"\n    logger.debug(\"Helper.generate_random_string()\")\n    char_set = digits + ascii_letters\n    return \"\".join(random.choice(char_set) for _ in range(length))\n\n\ndef jwk_thumbprint_get(logger: logging.Logger, pub_key: Dict[str, str]) -> str:\n    \"\"\"get thumbprint\"\"\"\n    logger.debug(\"Helper.jwk_thumbprint_get()\")\n    if pub_key:\n        try:\n            jwkey = jwk.JWK(**pub_key)\n            thumbprint = jwkey.thumbprint()\n        except Exception as err:\n            logger.error(\"Could not get the JWKEY thumbprint from public key: %s\", err)\n            thumbprint = None\n    else:\n        thumbprint = None\n\n    logger.debug(\"Helper.jwk_thumbprint_get() ended with: %s\", thumbprint)\n    return thumbprint\n\n\ndef sha256_hash(logger: logging.Logger, string: str) -> str:\n    \"\"\"hash string\"\"\"\n    logger.debug(\"Helper.sha256_hash()\")\n    result = hashlib.sha256(string.encode(\"utf-8\")).digest()\n    logger.debug(\n        \"Helper.sha256_hash() ended with %s (base64-encoded)\",\n        b64_encode(logger, result),\n    )\n    return result\n\n\ndef sha256_hash_hex(logger: logging.Logger, string: str) -> str:\n    \"\"\"hash string\"\"\"\n    logger.debug(\"Helper.sha256_hash_hex()\")\n    result = hashlib.sha256(string.encode(\"utf-8\")).hexdigest()\n    logger.debug(\"Helper.sha256_hash_hex() ended with %s\", result)\n    return result\n\n\ndef signature_check(\n    logger: logging.Logger, message: str, pub_key: str, json_: bool = False\n) -> Tuple[bool, str]:\n    \"\"\"check JWS\"\"\"\n    logger.debug(\"Helper.signature_check(%s)\", json_)\n\n    result = False\n    error = None\n\n    if pub_key:\n        # load key\n        try:\n            if json_:\n                logger.debug(\"Helper.signature_check(): load key from json\")\n                jwkey = jwk.JWK.from_json(pub_key)\n            else:\n                logger.debug(\"Helper.signature_check(): load plain json\")\n                jwkey = jwk.JWK(**pub_key)\n        except Exception as err:\n            logger.error(\"Loading of public key failed %s\", err)\n            jwkey = None\n            result = False\n            error = str(err)\n\n        # verify signature\n        if jwkey:\n            jwstoken = jws.JWS()\n            jwstoken.deserialize(message)\n            try:\n                jwstoken.verify(jwkey)\n                result = True\n            except Exception as err:\n                logger.error(\"Message verification failed %s\", err)\n                error = str(err)\n        else:\n            logger.error(\"No jwkey extracted\")\n    else:\n        logger.error(\"No pubkey specified.\")\n        error = \"No key specified.\"\n\n    logger.debug(\"Helper.signature_check() ended with: %s, %s\", result, error)\n    return (result, error)\n\n\ndef string_sanitize(logger: logging.Logger, unsafe_str: str) -> str:\n    \"\"\"sanitize string\"\"\"\n    logger.debug(\"Helper.string_sanitize()\")\n    allowed_range = set(range(32, 127))\n    safe_str = \"\"\n    for char in unsafe_str:\n        cp_ = ord(char)\n        if cp_ in allowed_range:\n            safe_str += char\n        elif cp_ == 9:\n            safe_str += \" \" * 4\n    return re.sub(r\"\\s+\", \" \", safe_str)\n"
  },
  {
    "path": "acme_srv/helpers/csr.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"CSR utilities for acme2certifier\"\"\"\nimport base64\nimport logging\nfrom typing import List, Dict\nfrom cryptography import x509\nfrom cryptography.hazmat.primitives import serialization\nfrom .encoding import (\n    convert_string_to_byte,\n    convert_byte_to_string,\n    build_pem_file,\n    b64_url_recode,\n    b64_encode,\n)\n\n\ndef csr_load(logger: logging.Logger, csr: str) -> x509.CertificateSigningRequest:\n    \"\"\"load certificate object from pem _Format\"\"\"\n    logger.debug(\"Helper.cert_load()\")\n\n    pem_data = convert_string_to_byte(\n        build_pem_file(logger, None, b64_url_recode(logger, csr), True, True)\n    )\n    csr_data = x509.load_pem_x509_csr(pem_data)\n\n    return csr_data\n\n\ndef csr_cn_get(logger: logging.Logger, csr_pem: str) -> str:\n    \"\"\"get cn from certificate request\"\"\"\n    logger.debug(\"Helper.csr_cn_get()\")\n\n    csr = csr_load(logger, csr_pem)\n    # Extract the subject's common name\n    common_name = None\n    for attribute in csr.subject:\n        if attribute.oid == x509.NameOID.COMMON_NAME:\n            common_name = attribute.value\n            break\n\n    logger.debug(\"Helper.csr_cn_get() ended with: %s\", common_name)\n    return common_name\n\n\ndef csr_dn_get(logger: logging.Logger, csr: str) -> str:\n    \"\"\"get subject from certificate request in openssl notation\"\"\"\n    logger.debug(\"Helper.csr_dn_get()\")\n\n    csr_obj = csr_load(logger, csr)\n    subject = csr_obj.subject.rfc4514_string()\n\n    logger.debug(\"Helper.csr_dn_get() ended with: %s\", subject)\n    return subject\n\n\ndef csr_pubkey_get(logger: logging.Logger, csr, encoding=\"pem\"):\n    \"\"\"get public key from certificate request\"\"\"\n    logger.debug(\"Helper.csr_pubkey_get()\")\n    csr_obj = csr_load(logger, csr)\n    public_key = csr_obj.public_key()\n    if encoding == \"pem\":\n        pubkey_str = public_key.public_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PublicFormat.SubjectPublicKeyInfo,\n        )\n        pubkey = convert_byte_to_string(pubkey_str)\n    elif encoding == \"base64der\":\n        pubkey_str = public_key.public_bytes(\n            encoding=serialization.Encoding.DER,\n            format=serialization.PublicFormat.SubjectPublicKeyInfo,\n        )\n        pubkey = b64_encode(logger, pubkey_str)\n\n    elif encoding == \"der\":\n        pubkey = public_key.public_bytes(\n            encoding=serialization.Encoding.DER,\n            format=serialization.PublicFormat.SubjectPublicKeyInfo,\n        )\n    else:\n        pubkey = None\n    logger.debug(\"Helper.cert_pubkey_get() ended with: %s\", pubkey)\n    return pubkey\n\n\ndef csr_san_get(logger: logging.Logger, csr: str) -> List[str]:\n    \"\"\"get subject alternate names from certificate\"\"\"\n    logger.debug(\"Helper.cert_san_get()\")\n    sans = []\n    if csr:\n\n        csr_obj = csr_load(logger, csr)\n        sans = []\n        try:\n            ext = csr_obj.extensions.get_extension_for_oid(\n                x509.OID_SUBJECT_ALTERNATIVE_NAME\n            )\n\n            sans_list = ext.value.get_values_for_type(x509.DNSName)\n            for san in sans_list:\n                sans.append(f\"DNS:{san}\")\n            sans_list = ext.value.get_values_for_type(x509.IPAddress)\n            for san in sans_list:\n                sans.append(f\"IP:{san}\")\n\n        except Exception as err:\n            logger.error(\"Error while getting SANs from CSR: %s\", err)\n\n    logger.debug(\"Helper.csr_san_get() ended with: %s\", str(sans))\n    return sans\n\n\ndef csr_san_byte_get(logger: logging.Logger, csr: str) -> bytes:\n    \"\"\"get sans from CSR as base64 encoded byte squence\"\"\"\n    # Load the CSR\n    logger.debug(\"Helper.csr_san_byte_get()\")\n\n    csr = csr_load(logger, csr)\n\n    # Get the SAN extension\n    san_extension = csr.extensions.get_extension_for_oid(\n        x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME\n    )\n\n    # Get the SANs\n    sans = san_extension.value\n\n    # Serialize the SANs to a byte sequence\n    sans_bytes = sans.public_bytes()\n\n    # Encode the byte sequence as base64\n    sans_base64 = b64_encode(logger, sans_bytes)\n\n    logger.debug(\"Helper.csr_san_byte_get() ended\")\n    return sans_base64\n\n\ndef csr_extensions_get(logger: logging.Logger, csr: str) -> List[str]:\n    \"\"\"get extensions from certificate\"\"\"\n    logger.debug(\"Helper.csr_extensions_get()\")\n\n    csr_obj = csr_load(logger, csr)\n\n    extension_list = []\n    for extension in csr_obj.extensions:\n        extension_list.append(\n            convert_byte_to_string(base64.b64encode(extension.value.public_bytes()))\n        )\n\n    logger.debug(\"Helper.csr_extensions_get() ended with: %s\", extension_list)\n    return extension_list\n\n\ndef csr_subject_get(logger: logging.Logger, csr: str) -> Dict[str, str]:\n    \"\"\"get subject from csr as a list of tuples\"\"\"\n    logger.debug(\"Helper.csr_subject_get()\")\n    # pylint: disable=w0212\n\n    # extend OID library from cryptography module\n    OID_NAME_MAP = {\n        \"2.5.4.97\": \"organizationIdentifier\",\n    }\n\n    csr_obj = csr_load(logger, csr)\n    subject_dic = {}\n    # get subject and look for common name\n    subject = csr_obj.subject\n    for attr in subject:\n        # use mapping table as primiary source, otherwise oid names from library\n        name = OID_NAME_MAP.get(attr.oid.dotted_string, attr.oid._name)\n        subject_dic[name] = attr.value\n\n    logger.debug(\"Helper.csr_subject_get() ended\")\n    return subject_dic\n\n\ndef csr_cn_lookup(logger: logging.Logger, csr: str) -> str:\n    \"\"\"lookup  CN/ 1st san from CSR\"\"\"\n    logger.debug(\"Heloer._csr_cn_lookup()\")\n\n    csr_cn = csr_cn_get(logger, csr)\n    if not csr_cn:\n        # lookup first san\n        san_list = csr_san_get(logger, csr)\n        if san_list and len(san_list) > 0:\n            for san in san_list:\n                try:\n                    csr_cn = san.split(\":\")[1]\n                    break\n                except Exception as err:\n                    logger.error(\"SAN split failed: %s\", err)\n        else:\n            logger.error(\"No SANs found in CSR\")\n\n    logger.debug(\"Helper._csr_cn_lookup() ended with: %s\", csr_cn)\n    return csr_cn\n"
  },
  {
    "path": "acme_srv/helpers/datetime_utils.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Date and time utilities for acme2certifier\"\"\"\nimport calendar\nimport datetime\nimport pytz\nfrom dateutil.parser import parse\n\n\ndef uts_now():\n    \"\"\"unixtimestamp in utc\"\"\"\n    return calendar.timegm(datetime.datetime.now(datetime.timezone.utc).utctimetuple())\n\n\ndef uts_to_date_utc(uts: int, tformat: str = \"%Y-%m-%dT%H:%M:%SZ\") -> str:\n    \"\"\"convert unix timestamp to date format\"\"\"\n    return datetime.datetime.fromtimestamp(int(uts), tz=pytz.utc).strftime(tformat)\n\n\ndef date_to_uts_utc(date_human: str, _tformat: str = \"%Y-%m-%dT%H:%M:%S\") -> int:\n    \"\"\"convert date to unix timestamp\"\"\"\n    if isinstance(date_human, datetime.datetime):\n        # we already got an datetime object as input\n        result = calendar.timegm(date_human.timetuple())\n    else:\n        result = int(calendar.timegm(parse(date_human).timetuple()))\n    return result\n\n\ndef date_to_datestr(\n    date: datetime.datetime, tformat: str = \"%Y-%m-%dT%H:%M:%SZ\"\n) -> str:\n    \"\"\"convert dateobj to datestring\"\"\"\n    try:\n        result = date.strftime(tformat)\n    except Exception:\n        result = None\n    return result\n\n\ndef datestr_to_date(datestr: str, tformat: str = \"%Y-%m-%dT%H:%M:%S\") -> str:\n    \"\"\"convert datestr to dateobj\"\"\"\n    try:\n        result = datetime.datetime.strptime(datestr, tformat)\n    except Exception:\n        result = None\n    return result\n"
  },
  {
    "path": "acme_srv/helpers/domain_utils.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Domain utilities for acme2certifier\"\"\"\nimport logging\nfrom typing import List, Tuple\nimport idna\nfrom .csr import csr_san_get, csr_cn_get\n\n\ndef encode_domain(logger, domain: str) -> bytes:\n    \"\"\"encode domain\"\"\"\n    logger.debug(\"Helper.encode_domain(%s)\", domain)\n\n    # Handle wildcard input *before* IDNA decoding.\n    if domain.startswith(\"*.\"):\n        domain = domain[2:]\n\n    encoded_domain = None\n    try:\n        encoded_domain = idna.encode(domain)\n    except Exception as err:\n        logger.error(f\"Invalid domain format in csr: {err}\")\n\n    return encoded_domain\n\n\ndef wildcard_domain_check(\n    logger: logging.Logger, domain: str, encoded_domain: str, encoded_pattern_base: str\n) -> bool:\n    \"\"\"compare domain to whitelist returns false if not matching\"\"\"\n    logger.debug(\"Helper.domain_check(%s)\", domain)\n\n    result = False\n    if domain.startswith(\"*.\"):\n        # Both input and pattern are wildcards. Check if input domain base includes the pattern\n        if encoded_domain.endswith(encoded_pattern_base):\n            result = True\n    else:\n        # Input is not a wildcard, pattern is. Check endswith. Add '.' to pattern base so it's not approving the base domain\n        # for example domain foo.bar shouldn't match with pattern *.foo.bar\n        if encoded_domain.endswith(b\".\" + encoded_pattern_base):\n            result = True\n    logger.debug(\"Helper.domain_check() ended with %s\", result)\n    return result\n\n\ndef pattern_check(logger, domain, pattern):\n    \"\"\"compare domain to whitelist returns false if not matching\"\"\"\n    logger.debug(\"Helper.pattern_check(%s, %s)\", domain, pattern)\n\n    pattern = pattern.lower().strip()\n    encoded_pattern = encode_domain(logger, pattern)\n    encoded_domain = encode_domain(logger, domain)\n\n    result = False\n    if encoded_pattern:\n        if pattern.startswith(\"*.\"):\n            result = wildcard_domain_check(\n                logger, domain, encoded_domain, encoded_pattern\n            )\n        else:\n            if not domain.startswith(\"*.\") and encoded_domain == encoded_pattern:\n                result = True\n    else:\n        logger.error(f\"Invalid pattern configured in allowed_domainlist: {pattern}\")\n\n    logger.debug(\"Helper.pattern_check() ended with %s\", result)\n    return result\n\n\ndef is_domain_whitelisted(\n    logger: logging.Logger, domain: str, whitelist: List[str]\n) -> bool:\n    \"\"\"compare domain to whitelist returns false if not matching\"\"\"\n    logger.debug(\"Helper.is_domain_whitelisted(%s)\", domain)\n\n    result = False\n    if domain:\n        domain = domain.lower().strip()\n        for pattern in whitelist:\n            # corner-case blank entry\n            if not pattern:\n                logger.error(\n                    \"Invalid pattern configured in allowed_domainlist: empty string\"\n                )\n                continue\n            result = pattern_check(logger, domain, pattern)\n            if result:\n                break\n\n    logger.debug(\"Helper.is_domain_whitelisted() ended with %s\", result)\n    return result\n\n\ndef allowed_domainlist_check(\n    logger: logging.Logger, csr, allowed_domain_list: List[str]\n) -> str:\n    \"\"\"check if domain is in allowed domain list\"\"\"\n    logger.debug(\"Helper.allowed_domainlist_check()\")\n\n    error = None\n    if allowed_domain_list:\n        (san_list, check_list) = sancheck_lists_create(logger, csr)\n\n        # clean email addresses\n        tmp_san_list = []\n        for san in san_list:\n            if \"@\" in san:\n                _email_name, email_domain = san.split(\"@\", 1)\n                tmp_san_list.append(email_domain)\n            else:\n                tmp_san_list.append(san)\n        san_list = tmp_san_list\n\n        invalid_domains = []\n\n        # go over the san list and check each entry\n        for san in san_list:\n            if not is_domain_whitelisted(logger, san, allowed_domain_list):\n                invalid_domains.append(san)\n                error = \"Either CN or SANs are not allowed by configuration\"\n\n        if check_list:\n            error = f\"SAN list parsing failed {check_list}\"\n\n        logger.debug(\n            f'Helper.allowed_domainlist_check() ended with {error} for {\",\".join(invalid_domains)}'\n        )\n    return error\n\n\ndef sancheck_lists_create(logger, csr: str) -> Tuple[List[str], List[str]]:\n    \"\"\"create lists for san check\"\"\"\n    logger.debug(\"Helper.sancheck_lists_create()\")\n\n    check_list = []\n    san_list = []\n\n    # get sans and build a list\n    _san_list = csr_san_get(logger, csr)\n\n    if _san_list:\n        for san in _san_list:\n            try:\n                # SAN list must be modified/filtered)\n                (_san_type, san_value) = san.lower().split(\":\")\n                san_list.append(san_value)\n            except Exception:\n                # force check to fail as something went wrong during parsing\n                check_list.append(san)\n                logger.debug(\n                    \"Helper.sancheck_lists_create(): san_list parsing failed at entry: %s\",\n                    san,\n                )\n\n    # get common name and attach it to san_list\n    cn = csr_cn_get(logger, csr)\n\n    if cn:\n        cn = cn.lower()\n        if cn not in san_list:\n            # append cn to san_list\n            logger.debug(\"Helper.sancheck_lists_create()): append cn to san_list\")\n            san_list.append(cn)\n\n    return (san_list, check_list)\n"
  },
  {
    "path": "acme_srv/helpers/eab.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"EAB (External Account Binding) utilities for acme2certifier\"\"\"\nimport logging\nfrom typing import Optional\nfrom .csr import csr_subject_get\nfrom .encoding import b64_url_recode\nfrom .config import client_parameter_validate, profile_lookup, header_info_lookup\nfrom .validation import cn_validate\nfrom .domain_utils import allowed_domainlist_check\n\n\ndef _handle_eab_profiling(\n    logger: logging.Logger, cahandler, csr: str, handler_hifield: str\n) -> Optional[str]:\n    \"\"\"Handle EAB profiling logic\"\"\"\n    logger.debug(\"Helper._handle_eab_profiling()\")\n\n    if not (hasattr(cahandler, \"eab_handler\") and cahandler.eab_handler):\n        logger.error(\"EAB profiling enabled but no handler defined\")\n        return \"Eab_profiling enabled but no handler defined\"\n\n    # profiling enabled - check profile\n    return eab_profile_check(logger, cahandler, csr, handler_hifield)\n\n\ndef _handle_acme_profiling(\n    logger: logging.Logger, cahandler, csr: str, handler_hifield: str\n) -> None:\n    \"\"\"Handle ACME profiling logic\"\"\"\n    logger.debug(\"Helper._handle_acme_profiling()\")\n\n    profile = profile_lookup(logger, csr)\n    if profile:\n        logger.debug(\n            \"Helper.profile_lookup(): setting %s to %s\",\n            handler_hifield,\n            profile,\n        )\n        setattr(cahandler, handler_hifield, profile)\n\n\ndef _handle_header_info_profiling(\n    logger: logging.Logger, cahandler, csr: str, handler_hifield: str\n) -> None:\n    \"\"\"Handle header info profiling logic\"\"\"\n    logger.debug(\"Helper._handle_header_info_profiling()\")\n\n    hil_value = header_info_lookup(\n        logger, csr, cahandler.header_info_field, handler_hifield\n    )\n    if hil_value:\n        logger.debug(\n            \"Helper.eab_profile_header_info_check(): setting %s to %s\",\n            handler_hifield,\n            hil_value,\n        )\n        logger.info(\n            \"Received enrollment parameter: %s value: %s via headerinfo field\",\n            handler_hifield,\n            hil_value,\n        )\n        setattr(cahandler, handler_hifield, hil_value)\n    else:\n        logger.debug(\"eab_profile_header_info_check(): no header_info field found\")\n\n\ndef eab_profile_header_info_check(\n    logger: logging.Logger,\n    cahandler,\n    csr: str,\n    handler_hifield: str = \"profile_name\",\n) -> Optional[str]:\n    \"\"\"check profile\"\"\"\n    logger.debug(\"Helper.eab_profile_header_info_check()\")\n\n    # Priority 1: EAB profiling - returns error string or None\n    if hasattr(cahandler, \"eab_profiling\") and cahandler.eab_profiling:\n        error = _handle_eab_profiling(logger, cahandler, csr, handler_hifield)\n\n    # Priority 2: ACME profiling (preferred over header info) - no errors\n    elif hasattr(cahandler, \"profiles\") and cahandler.profiles:\n        _handle_acme_profiling(logger, cahandler, csr, handler_hifield)\n        error = None\n\n    # Priority 3: Header info profiling - no errors\n    elif hasattr(cahandler, \"header_info_field\") and cahandler.header_info_field:\n        _handle_header_info_profiling(logger, cahandler, csr, handler_hifield)\n        error = None\n\n    # Priority 4: No profiling\n    else:\n        error = None\n\n    logger.debug(\"Helper.eab_profile_header_info_check() ended with %s\", error)\n    return error\n\n\ndef eab_profile_subject_string_check(\n    logger: logging.Logger, profile_subject_dic, key: str, value: str\n) -> str:\n    \"\"\"check if a for a string value taken from profile if its a variable inside a class and apply value\"\"\"\n    logger.debug(\n        \"Helper.eab_profile_subject_string_check(): string: key: %s, value: %s\",\n        key,\n        value,\n    )\n\n    error = False\n    if key == \"commonName\":\n        # check if CN is a valid IP address or fqdn\n        error = cn_validate(logger, value)\n    elif key in profile_subject_dic:\n        if isinstance(profile_subject_dic[key], str) and (\n            value == profile_subject_dic[key] or profile_subject_dic[key] == \"*\"\n        ):\n            logger.debug(\n                \"Helper.eab_profile_subject_check() successul for string : %s\", key\n            )\n            del profile_subject_dic[key]\n        elif (\n            isinstance(profile_subject_dic[key], list)\n            and value in profile_subject_dic[key]\n        ):\n            logger.debug(\n                \"Helper.eab_profile_subject_check() successul for list : %s\", key\n            )\n            del profile_subject_dic[key]\n        else:\n            logger.error(\n                \"EAB profile subject check failed for: %s: value: %s expected: %s\",\n                key,\n                value,\n                profile_subject_dic[key],\n            )\n            error = f\"Profile subject check failed for {key}\"\n    else:\n        logger.error(\"EAB profile subject failed for: %s\", key)\n        error = f\"Profile subject check failed for {key}\"\n\n    logger.debug(\"Helper.eab_profile_subject_string_check() ended\")\n    return error\n\n\ndef eab_profile_subject_check(\n    logger: logging.Logger, csr: str, profile_subject_dic: str\n) -> str:\n    \"\"\"check subject against profile information\"\"\"\n    logger.debug(\"Helper.eab_profile_subject_check()\")\n    error = None\n\n    # get subject from csr\n    subject_dic = csr_subject_get(logger, csr)\n\n    # check if all profile subject entries are in csr\n    for key, value in subject_dic.items():\n        error = eab_profile_subject_string_check(\n            logger, profile_subject_dic, key, value\n        )\n        if error:\n            break\n\n    # check if we have any entries left in the profile_subject_dic\n    if not error and profile_subject_dic:\n        logger.error(\n            \"EAB profile subject check failed for: %s\",\n            list(profile_subject_dic.keys()),\n        )\n        error = \"Profile subject check failed\"\n\n    logger.debug(\"Helper.eab_profile_subject_check() ended with: %s\", error)\n    return error\n\n\ndef eab_profile_revocation_check(\n    logger: logging.Logger, cahandler, certificate_raw: str\n):\n    \"\"\"check eab profile for revocation\"\"\"\n    logger.debug(\"Helper.eab_profile_revocation_check()\")\n    with cahandler.eab_handler(logger) as eab_handler:\n        eab_profile_dic = eab_handler.eab_profile_get(\n            b64_url_recode(logger, certificate_raw), revocation=True\n        )\n        for key, value in eab_profile_dic.items():\n            if key in [\"subject\", \"allowed_domainlist\"]:\n                continue\n            elif isinstance(value, str):\n                eab_profile_string_check(logger, cahandler, key, value)\n            elif isinstance(value, list):\n                # check if we need to execute a function from the handler\n                if \"eab_profile_list_check\" in dir(cahandler):\n                    _result = cahandler.eab_profile_list_check(\n                        eab_handler, certificate_raw, key, value\n                    )\n                else:\n                    _result = eab_profile_list_check(\n                        logger, cahandler, eab_handler, certificate_raw, key, value\n                    )\n\n    logger.debug(\"Helper.eab_profile_revocation_check() ended\")\n\n\ndef eab_profile_check(\n    logger: logging.Logger, cahandler, csr: str, handler_hifield: str\n) -> str:\n    \"\"\"check eab profile\"\"\"\n    logger.debug(\"Helper.eab_profile_check()\")\n\n    result = None\n    with cahandler.eab_handler(logger) as eab_handler:\n        eab_profile_dic = eab_handler.eab_profile_get(csr)\n        for key, value in eab_profile_dic.items():\n            if key == \"subject\":\n                result = eab_profile_subject_check(logger, csr, value)\n            elif isinstance(value, str):\n                eab_profile_string_check(logger, cahandler, key, value)\n            elif isinstance(value, list):\n                # check if we need to execute a function from the handler\n                if \"eab_profile_list_check\" in dir(cahandler):\n                    result = cahandler.eab_profile_list_check(\n                        eab_handler, csr, key, value\n                    )\n                else:\n                    result = eab_profile_list_check(\n                        logger, cahandler, eab_handler, csr, key, value\n                    )\n            if result:\n                break\n\n        # we need to reject situations where profiling is enabled but the header_hifiled is not defined in json\n        if cahandler.header_info_field and handler_hifield not in eab_profile_dic:\n            hil_value = header_info_lookup(\n                logger, csr, cahandler.header_info_field, handler_hifield\n            )\n            if hil_value:\n                # setattr(self, handler_hifield, hil_value)\n                result = (\n                    f'header_info field \"{handler_hifield}\" is not allowed by profile'\n                )\n\n    logger.debug(\"Helper.eab_profile_check() ended with: %s\", result)\n    return result\n\n\ndef eab_profile_list_check(logger, cahandler, eab_handler, csr, key, value):\n    \"\"\"check if a for a list value taken from profile if its a variable inside a class and apply value\"\"\"\n    logger.debug(\n        \"Helper.eab_profile_list_check(): list: key: %s, value: %s\", key, value\n    )\n\n    result = None\n    if hasattr(cahandler, key) and key != \"allowed_domainlist\":\n        new_value, error = client_parameter_validate(logger, csr, cahandler, key, value)\n        if new_value:\n            logger.debug(\n                \"Helper.eab_profile_list_check(): setting attribute: %s to %s\",\n                key,\n                new_value,\n            )\n            setattr(cahandler, key, new_value)\n        else:\n            result = error\n    elif key == \"allowed_domainlist\":\n        # check if csr contains allowed domains\n        if \"allowed_domains_check\" in dir(eab_handler):\n            # execute a function from eab_handler\n            logger.info(\"Execute allowed_domains_check() from eab handler\")\n            error = eab_handler.allowed_domains_check(csr, value)\n        else:\n            # execute default adl function from helper\n            logger.debug(\n                \"Helper.eab_profile_list_check(): execute default allowed_domainlist_check()\"\n            )\n            error = allowed_domainlist_check(logger, csr, value)\n        if error:\n            result = error\n    else:\n        logger.warning(\n            \"EAP profile list checking: ignoring unrecognized list attribute: key: %s value: %s\",\n            key,\n            value,\n        )\n\n    logger.debug(\"Helper.eab_profile_list_check() ended with: %s\", result)\n    return result\n\n\ndef eab_profile_string_check(logger, cahandler, key, value):\n    \"\"\"check if a for a string value taken from profile if its a variable inside a class and apply value\"\"\"\n    logger.debug(\n        \"Helper.eab_profile_string_check(): string: key: %s, value: %s\", key, value\n    )\n\n    if hasattr(cahandler, key):\n        logger.debug(\n            \"Helper.eab_profile_string_check(): setting attribute: %s to %s\", key, value\n        )\n        setattr(cahandler, key, value)\n    else:\n        logger.warning(\n            \"EAB profile string checking: ignoring unrecognized string attribute: key: %s value: %s\",\n            key,\n            value,\n        )\n\n    logger.debug(\"Helper.eab_profile_string_check() ended\")\n"
  },
  {
    "path": "acme_srv/helpers/encoding.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Encoding and base64 utilities for acme2certifier\"\"\"\nimport base64\nimport textwrap\nimport logging\n\n\ndef b64decode_pad(logger: logging.Logger, string: str) -> bytes:\n    \"\"\"b64 decoding and padding of missing \"=\" \"\"\"\n    logger.debug(\"Helper.b64decode_pad()\")\n    try:\n        b64dec = base64.urlsafe_b64decode(string + \"=\" * (4 - len(string) % 4))\n    except Exception:\n        b64dec = b\"ERR: b64 decoding error\"\n    return b64dec.decode(\"utf-8\")\n\n\ndef b64_decode(logger: logging.Logger, string: str) -> str:\n    \"\"\"b64 decoding\"\"\"\n    logger.debug(\"Helper.b64decode()\")\n    return convert_byte_to_string(base64.b64decode(string))\n\n\ndef b64_encode(logger: logging.Logger, string: str) -> str:\n    \"\"\"encode a bytestream in base64\"\"\"\n    logger.debug(\"Helper.b64_encode()\")\n    return convert_byte_to_string(base64.b64encode(string))\n\n\ndef b64_url_encode(logger: logging.Logger, string: str) -> str:\n    \"\"\"encode a bytestream in base64 url and remove padding\"\"\"\n    logger.debug(\"Helper.b64_url_encode()\")\n    string = convert_string_to_byte(string)\n    encoded = base64.urlsafe_b64encode(string)\n    return encoded.rstrip(b\"=\")\n\n\ndef b64_url_recode(logger: logging.Logger, string: str) -> str:\n    \"\"\"recode base64_url to base64\"\"\"\n    logger.debug(\"Helper.b64_url_recode()\")\n    padding_factor = (4 - len(string) % 4) % 4\n    string = convert_byte_to_string(string)\n    string += \"=\" * padding_factor\n    result = str(string).translate(dict(zip(map(ord, \"-_\"), \"+/\")))\n    return result\n\n\ndef b64_url_decode(logger: logging.Logger, string: str) -> str:\n    \"\"\"decode base64url encoded string\"\"\"\n    logger.debug(\"Helper.b64_url_decode()\")\n    # Remove whitespace\n    string = string.strip()\n    # Add padding if missing\n    pad = \"=\" * (-len(string) % 4)\n    string_padded = string + pad\n    return convert_byte_to_string(base64.urlsafe_b64decode(string_padded))\n\n\ndef build_pem_file(logger: logging.Logger, existing, certificate, wrap, csr=False):\n    \"\"\"construct pem_file\"\"\"\n    logger.debug(\"Helper.build_pem_file()\")\n    if csr:\n        pem_file = f\"-----BEGIN CERTIFICATE REQUEST-----\\n{textwrap.fill(convert_byte_to_string(certificate), 64)}\\n-----END CERTIFICATE REQUEST-----\\n\"\n    else:\n        if existing:\n            if wrap:\n                pem_file = f\"{existing}-----BEGIN CERTIFICATE-----\\n{textwrap.fill(convert_byte_to_string(certificate), 64)}\\n-----END CERTIFICATE-----\\n\"\n            else:\n                pem_file = f\"{convert_byte_to_string(existing)}-----BEGIN CERTIFICATE-----\\n{convert_byte_to_string(certificate)}\\n-----END CERTIFICATE-----\\n\"\n        else:\n            if wrap:\n                pem_file = f\"-----BEGIN CERTIFICATE-----\\n{textwrap.fill(convert_byte_to_string(certificate), 64)}\\n-----END CERTIFICATE-----\\n\"\n            else:\n                pem_file = f\"-----BEGIN CERTIFICATE-----\\n{convert_byte_to_string(certificate)}\\n-----END CERTIFICATE-----\\n\"\n    return pem_file\n\n\ndef convert_byte_to_string(value: bytes) -> str:\n    \"\"\"convert a variable to string if needed\"\"\"\n    if hasattr(value, \"decode\"):\n        try:\n            return value.decode()\n        except Exception:\n            return value\n    else:\n        return value\n\n\ndef convert_string_to_byte(value: str) -> bytes:\n    \"\"\"convert a variable to byte if needed\"\"\"\n    if hasattr(value, \"encode\"):\n        result = value.encode()\n    else:\n        result = value\n    return result\n"
  },
  {
    "path": "acme_srv/helpers/global_variables.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Global Variables for acme2certifier\"\"\"\n\n\nPARSING_ERR_MSG = \"failed to parse\"\nUSER_AGENT = \"acme2certifier\"\n"
  },
  {
    "path": "acme_srv/helpers/logging_utils.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Logging utilities for acme2certifier\"\"\"\nimport logging\nimport sys\nimport copy\nfrom typing import Dict\nimport datetime\nfrom .config import load_config\n\n\ndef _logger_nonce_modify(data_dic: Dict[str, str]) -> Dict[str, str]:\n    \"\"\"remove nonce from log entry\"\"\"\n    if \"header\" in data_dic and \"Replay-Nonce\" in data_dic[\"header\"]:\n        data_dic[\"header\"][\"Replay-Nonce\"] = \"- modified -\"\n    return data_dic\n\n\ndef _logger_certificate_modify(\n    data_dic: Dict[str, str], locator: str\n) -> Dict[str, str]:\n    \"\"\"remove cert from log entry\"\"\"\n    if \"/acme/cert\" in locator:\n        data_dic[\"data\"] = \" - certificate - \"\n    return data_dic\n\n\ndef _logger_token_modify(data_dic: Dict[str, str]) -> Dict[str, str]:\n    \"\"\"remove token from challenge\"\"\"\n    if \"token\" in data_dic[\"data\"]:\n        data_dic[\"data\"][\"token\"] = \"- modified -\"\n    return data_dic\n\n\ndef _logger_challenges_modify(data_dic: Dict[str, str]) -> Dict[str, str]:\n    \"\"\"remove token from challenge\"\"\"\n    if \"challenges\" in data_dic[\"data\"]:\n        for challenge in data_dic[\"data\"][\"challenges\"]:\n            if \"token\" in challenge:\n                challenge.update(\n                    (k, \"- modified - \") for k, v in challenge.items() if k == \"token\"\n                )\n    return data_dic\n\n\ndef logger_info(\n    logger: logging.Logger, addr: str, locator: str, dat_dic: Dict[str, str]\n):\n    \"\"\"log responses\"\"\"\n    # create a copy of the dictionary\n    data_dic = copy.deepcopy(dat_dic)\n\n    data_dic = _logger_nonce_modify(data_dic)\n    if \"data\" in data_dic:\n        # remove cert from log entry\n        data_dic = _logger_certificate_modify(data_dic, locator)\n\n        # remove token\n        data_dic = _logger_token_modify(data_dic)\n\n        # remove token from challenge\n        data_dic = _logger_challenges_modify(data_dic)\n\n    logger.info(\"%s %s %s\", addr, locator, str(data_dic))\n\n\ndef logger_setup(debug: bool) -> logging.Logger:\n    \"\"\"setup logger\"\"\"\n    if debug:\n        log_mode = logging.DEBUG\n    else:\n        log_mode = logging.INFO\n\n    config_dic = load_config()\n\n    # define standard log format\n    log_format = \"%(message)s\"\n    if \"Helper\" in config_dic and \"log_format\" in config_dic[\"Helper\"]:\n        log_format = config_dic[\"Helper\"][\"log_format\"]\n\n    logging.basicConfig(format=log_format, datefmt=\"%Y-%m-%d %H:%M:%S\", level=log_mode)\n    logger = logging.getLogger(\"acme2certifier\")\n    return logger\n\n\ndef print_debug(debug: bool, text: str):\n    \"\"\"little helper to print debug messages\"\"\"\n    if debug:\n        print(f\"{datetime.datetime.now()}: {text}\")\n\n\ndef handle_exception(exc_type, exc_value, exc_traceback):  # pragma: no cover\n    \"\"\"exception handler\"\"\"\n    if issubclass(exc_type, KeyboardInterrupt):\n        sys.__excepthook__(exc_type, exc_value, exc_traceback)\n        return\n\n    logging.exception(\n        \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n    )\n"
  },
  {
    "path": "acme_srv/helpers/network.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Network utilities for acme2certifier\"\"\"\nimport html\nimport socket\nimport ssl\nimport logging\nimport json\nimport re\nfrom typing import List, Dict, Tuple, Union, Optional\nfrom urllib.parse import urlparse, quote\nfrom urllib3.util import connection\nimport socks\nimport dns.resolver\nimport requests\nimport requests.packages.urllib3.util.connection as urllib3_cn  # pylint: disable=E0401\nfrom .config import load_config\nfrom .validation import ipv6_chk\nfrom .encoding import convert_string_to_byte, b64_encode\nfrom .global_variables import USER_AGENT\n\n\ndef _handle_dns_exception(\n    logger: logging.Logger,\n    host: str,\n    rrtype: str,\n    error: Exception,\n    errors_encountered: List[str],\n) -> None:\n    \"\"\"Handle DNS resolution exceptions and log them appropriately\"\"\"\n    error_mappings = {\n        dns.resolver.NXDOMAIN: f\"NXDOMAIN: {host} does not exist\",\n        dns.resolver.NoAnswer: f\"No {rrtype} record found for {host}\",\n        dns.resolver.Timeout: f\"DNS query timeout for {host}\",\n    }\n\n    error_detail = error_mappings.get(\n        type(error), f\"DNS resolution error: {str(error)}\"\n    )\n\n    logger.debug(\"Error resolving %s with type %s: %s\", host, rrtype, error_detail)\n    errors_encountered.append(f\"{rrtype}: {error_detail}\")\n\n\ndef _process_dns_answers(\n    logger: logging.Logger,\n    answers: dns.resolver.Answer,\n    catch_all: bool,\n    result: Union[List[str], None],\n) -> Tuple[Union[str, List[str], None], bool]:\n    \"\"\"Process DNS resolution answers and return appropriate result\"\"\"\n    logger.debug(\"Helper._fqdn_resolve() got answer: %s\", list(answers))\n    resolved = [str(rdata) for rdata in answers]\n\n    if not resolved:\n        return result, True  # No answers found, keep searching\n\n    if catch_all:\n        if isinstance(result, list):\n            result.extend(resolved)\n        return result, False  # Found answers, mark as valid\n    else:\n        return resolved[0], False  # Return first answer and mark as valid\n\n\ndef _fqdn_resolve(\n    logger: logging.Logger,\n    req: dns.resolver.Resolver,\n    host: str,\n    catch_all: bool = False,\n) -> Tuple[Union[str, List[str], None], bool, Optional[str]]:\n    \"\"\"resolve hostname with detailed error reporting\"\"\"\n    logger.debug(\"Helper._fqdn_resolve(%s:%s)\", host, catch_all)\n\n    result = [] if catch_all else None\n    invalid = True\n    errors_encountered = []\n\n    for rrtype in [\"A\", \"AAAA\"]:\n        try:\n            answers = req.resolve(host, rrtype)\n            temp_result, temp_invalid = _process_dns_answers(\n                logger, answers, catch_all, result\n            )\n\n            if not temp_invalid:  # Only update if we got valid results\n                result = temp_result\n                invalid = False\n                if not catch_all:\n                    break  # Early exit for non-catch-all successful resolution\n        except (\n            dns.resolver.NXDOMAIN,\n            dns.resolver.NoAnswer,\n            dns.resolver.Timeout,\n            Exception,\n        ) as err:\n            _handle_dns_exception(logger, host, rrtype, err, errors_encountered)\n\n    # Combine errors if resolution failed\n    error_msg = (\n        \"; \".join(errors_encountered) if invalid and errors_encountered else None\n    )\n\n    logger.debug(\n        \"Helper._fqdn_resolve(%s) ended with: %s, %s, error: %s\",\n        host,\n        result,\n        invalid,\n        error_msg,\n    )\n    return (result, invalid, error_msg)\n\n\ndef fqdn_resolve(\n    logger: logging.Logger, host: str, dnssrv: List[str] = None, catch_all: bool = False\n) -> Tuple[Union[str, List[str], None], bool, Optional[str]]:\n    \"\"\"dns resolver with error reporting\"\"\"\n    logger.debug(\"Helper.fqdn_resolve(%s catch_all: %s)\", host, catch_all)\n    req = dns.resolver.Resolver()\n\n    # hack to cover github workflows\n    if \".\" in host:\n        if dnssrv:\n            # add specific dns server\n            req.nameservers = dnssrv\n        # resolve hostname\n        (result, invalid, error_msg) = _fqdn_resolve(\n            logger, req, host, catch_all=catch_all\n        )\n\n    else:\n        result = None\n        invalid = False\n        error_msg = None\n\n    logger.debug(\n        \"Helper.fqdn_resolve(%s) ended with: %s, %s, error: %s\",\n        host,\n        result,\n        invalid,\n        error_msg,\n    )\n    return (result, invalid, error_msg)\n\n\ndef ptr_resolve(\n    logger: logging.Logger, ip_address: str, dnssrv: List[str] = None\n) -> Tuple[str, bool]:\n    \"\"\"reverse dns resolver\"\"\"\n    logger.debug(\"Helper.ptr_resolve(%s)\", ip_address)\n    req = dns.resolver.Resolver()\n    invalid = True\n\n    if dnssrv:\n        # add specific dns server\n        req.nameservers = dnssrv\n    try:\n        reversed_dns = dns.reversename.from_address(ip_address)\n        answers = req.resolve(reversed_dns, \"PTR\")\n        result = str(answers[0])[:-1]  # remove trailing dot\n        invalid = False\n    except Exception as err:\n        logger.debug(\"Error while resolving %s: %s\", ip_address, err)\n        result = None\n\n    logger.debug(\"Helper.ptr_resolve(%s) ended with: %s\", ip_address, result)\n    return result, invalid\n\n\ndef dns_server_list_load() -> List[str]:\n    \"\"\"load dns-server from config file\"\"\"\n    config_dic = load_config()\n\n    # define default dns servers\n    default_dns_server_list = [\"9.9.9.9\", \"8.8.8.8\"]\n\n    if \"Challenge\" in config_dic:\n        if \"dns_server_list\" in config_dic[\"Challenge\"]:\n            try:\n                dns_server_list = json.loads(config_dic[\"Challenge\"][\"dns_server_list\"])\n            except Exception:\n                dns_server_list = default_dns_server_list\n        else:\n            dns_server_list = default_dns_server_list\n    else:\n        dns_server_list = default_dns_server_list\n\n    return dns_server_list\n\n\ndef patched_create_connection(address: List[str], *args, **kwargs):  # pragma: no cover\n    \"\"\"Wrap urllib3's create_connection to resolve the name elsewhere\"\"\"\n    # load dns-servers from config file\n    dns_server_list = dns_server_list_load()\n    # resolve hostname to an ip address; use your own resolver\n    host, port = address\n    (hostname, _invalid, _error) = fqdn_resolve(host, dns_server_list)\n    # pylint: disable=W0212\n    return connection._orig_create_connection((hostname, port), *args, **kwargs)\n\n\ndef proxy_check(\n    logger: logging.Logger, fqdn: str, proxy_server_list: Dict[str, str]\n) -> str:\n    \"\"\"check proxy server\"\"\"\n    logger.debug(\"Helper.proxy_check(%s)\", fqdn)\n\n    # remove leading *.\n    proxy_server_list_new = {\n        k.replace(\"*.\", \"\"): v for k, v in proxy_server_list.items()\n    }\n\n    proxy = None\n    for regex in sorted(proxy_server_list_new.keys(), reverse=True):\n        if regex != \"*\":\n            regex_compiled = re.compile(regex)\n            if bool(regex_compiled.search(fqdn)):\n                # parameter is in - set flag accordingly and stop loop\n                proxy = proxy_server_list_new[regex]\n                logger.debug(\n                    \"Helper.proxy_check() match found: fqdn: %s, regex: %s\", fqdn, regex\n                )\n                break\n\n    if \"*\" in proxy_server_list_new and not proxy:\n        logger.debug(\"Helper.proxy_check() wildcard match found: fqdn: %s\", fqdn)\n        proxy = proxy_server_list_new[\"*\"]\n\n    logger.debug(\"Helper.proxy_check() ended with %s\", proxy)\n    return proxy\n\n\ndef url_get_with_own_dns(\n    logger: logging.Logger, url: str, verify: bool = True\n) -> Tuple[Optional[str], int, Optional[str]]:\n    \"\"\"request by using an own dns resolver\"\"\"\n    logger.debug(\"Helper.url_get_with_own_dns(%s)\", url)\n    # patch an own connection handler into URL lib\n    # pylint: disable=W0212\n    connection._orig_create_connection = connection.create_connection\n    connection.create_connection = patched_create_connection\n    try:\n        req = requests.get(\n            url,\n            verify=verify,\n            headers={\n                \"Connection\": \"close\",\n                \"Accept-Encoding\": \"gzip\",\n                \"User-Agent\": USER_AGENT,\n            },\n            timeout=20,\n        )\n        result = req.text\n        status_code = req.status_code\n        if status_code != 200:\n            error_msg = f\"{url} {req.reason}\"\n        else:\n            error_msg = None\n    except Exception as err_:\n        result = None\n        status_code = 500\n        error_msg = (\n            f\"Could not get URL by using the configured DNS servers: {str(err_)}\"\n        )\n        logger.error(error_msg)\n    # cleanup\n    connection.create_connection = connection._orig_create_connection\n    return result, status_code, error_msg\n\n\ndef allowed_gai_family():\n    \"\"\"set family\"\"\"\n    family = socket.AF_INET  # force IPv4\n    return family\n\n\ndef url_get_with_default_dns(\n    logger: logging.Logger,\n    url: str,\n    proxy_list: Dict[str, str],\n    verify: bool,\n    timeout: int,\n) -> Tuple[Optional[str], int, Optional[str]]:\n    \"\"\"http get with default dns server\"\"\"\n    logger.debug(\n        \"Helper.url_get_with_default_dns(%s) vrf=%s, timout:%s\", url, verify, timeout\n    )\n\n    # we need to tweak headers and url for ipv6 addresse\n    (headers, url) = v6_adjust(logger, url)\n    try:\n        req = requests.get(\n            url, verify=verify, timeout=timeout, headers=headers, proxies=proxy_list\n        )\n        result = req.text\n        status_code = req.status_code\n        if status_code != 200:\n            error_msg = f\"{url} {req.reason}\"\n        else:\n            error_msg = None\n\n    except Exception as err_:\n        logger.debug(\"Helper.url_get_with_default_dns(%s): error\", err_)\n        # force fallback to ipv4\n        logger.debug(\"Helper.url_get_with_default_dns(%s): fallback to v4\", url)\n        old_gai_family = urllib3_cn.allowed_gai_family\n        try:\n            urllib3_cn.allowed_gai_family = allowed_gai_family\n            req = requests.get(\n                url,\n                verify=verify,\n                timeout=timeout,\n                headers={\n                    \"Connection\": \"close\",\n                    \"Accept-Encoding\": \"gzip\",\n                    \"User-Agent\": USER_AGENT,\n                },\n                proxies=proxy_list,\n            )\n            result = req.text\n            status_code = req.status_code\n            if status_code != 200:\n                error_msg = f\"{url} {req.reason}\"\n            else:\n                error_msg = None\n        except requests.exceptions.ReadTimeout as _errex:\n            logger.debug(\"Helper.url_get_with_default_dns(%s): read timeout\", url)\n            result = None\n            status_code = 500\n            error_msg = f\"Could not fetch URL: {url} - Read timeout.\"\n            logger.error(error_msg)\n        except requests.exceptions.ConnectionError as _errex:\n            logger.debug(\"Helper.url_get_with_default_dns(%s): connection error\", url)\n            result = None\n            status_code = 500\n            error_msg = f\"Could not fetch URL: {url} - Connection error.\"\n            logger.error(error_msg)\n        except Exception as err:\n            logger.debug(\"Helper.url_get_with_default_dns(%s): other error\", url)\n            result = None\n            status_code = 500\n            error_msg = f\"Could not fetch URL: {url}\"\n            logger.error(err)\n\n        urllib3_cn.allowed_gai_family = old_gai_family\n\n    return result, status_code, error_msg\n\n\ndef url_get(\n    logger: logging.Logger,\n    url: str,\n    dns_server_list: List[str] = None,\n    proxy_server=None,\n    verify=True,\n    timeout=20,\n) -> Tuple[Optional[str], int, Optional[str]]:\n    \"\"\"http get with enhanced error reporting\"\"\"\n    logger.debug(\"Helper.url_get(%s) vrf=%s, timout:%s\", url, verify, timeout)\n    # pylint: disable=w0621\n    # configure proxy servers if specified\n    if proxy_server:\n        proxy_list = {\"http\": proxy_server, \"https\": proxy_server}\n    else:\n        proxy_list = {}\n    if dns_server_list and not proxy_server:\n        result, status_code, error_msg = url_get_with_own_dns(logger, url, verify)\n    else:\n        result, status_code, error_msg = url_get_with_default_dns(\n            logger, url, proxy_list, verify, timeout\n        )\n\n    logger.debug(\n        \"Helper.url_get() ended with status: %s, error: %s\", status_code, error_msg\n    )\n    return result, status_code, error_msg\n\n\ndef txt_get(logger: logging.Logger, fqdn: str, dns_srv: List[str] = None) -> List[str]:\n    \"\"\"dns query to get the TXt record\"\"\"\n    logger.debug(\"Helper.txt_get(%s: %s)\", fqdn, dns_srv)\n\n    # rewrite dns resolver if configured\n    if dns_srv:\n        logger.debug(\"Helper.txt_get(): use custom dns servers: %s\", dns_srv)\n        dns.resolver.default_resolver = dns.resolver.Resolver(configure=False)\n        dns.resolver.default_resolver.nameservers = dns_srv\n    txt_record_list = []\n    try:\n        response = dns.resolver.resolve(fqdn, \"TXT\")\n        for rrecord in response:\n            txt_record_list.append(rrecord.strings[0])\n    except Exception as err_:\n        logger.error(\"Could not get TXT record: %s\", err_)\n    logger.debug(\"Helper.txt_get() ended with: %s\", txt_record_list)\n    return txt_record_list\n\n\ndef proxystring_convert(\n    logger: logging.Logger, proxy_server: str\n) -> Tuple[str, str, str]:\n    \"\"\"convert proxy string\"\"\"\n    logger.debug(\"Helper.proxystring_convert(%s)\", proxy_server)\n\n    proxy_proto_dic = {\n        \"http\": socks.PROXY_TYPE_HTTP,\n        \"socks4\": socks.PROXY_TYPE_SOCKS4,\n        \"socks5\": socks.PROXY_TYPE_SOCKS5,\n    }\n    try:\n        (proxy_proto, proxy) = proxy_server.split(\"://\")\n    except Exception:\n        logger.error(\n            \"Error while splitting proxy_server string: %s\",\n            proxy_server,\n        )\n        proxy = None\n        proxy_proto = None\n\n    if proxy:\n        try:\n            (proxy_addr, proxy_port) = proxy.split(\":\")\n        except Exception:\n            logger.error(\"Error while splitting proxy into host/port: %s\", proxy)\n            proxy_addr = None\n            proxy_port = None\n    else:\n        proxy_addr = None\n        proxy_port = None\n\n    if proxy_proto and proxy_addr and proxy_port:\n        try:\n            proto_string = proxy_proto_dic[proxy_proto]\n        except Exception:\n            logger.error(\"Unknown proxy protocol: %s\", proxy_proto)\n            proto_string = None\n    else:\n        logger.error(\n            \"proxy_proto (%s), proxy_addr (%s) or proxy_port (%s) missing\",\n            proxy_proto,\n            proxy_addr,\n            proxy_port,\n        )\n        proto_string = None\n\n    try:\n        proxy_port = int(proxy_port)\n    except Exception:\n        logger.error(\"Unknown proxy port: %s\", proxy_port)\n        proxy_port = None\n\n    logger.debug(\n        \"Helper.proxystring_convert() ended with %s, %s, %s\",\n        proto_string,\n        proxy_addr,\n        proxy_port,\n    )\n    return (proto_string, proxy_addr, proxy_port)\n\n\ndef servercert_get(\n    logger: logging.Logger,\n    hostname: str,\n    port: int = 443,\n    proxy_server: str = None,\n    sni: str = None,\n) -> str:\n    \"\"\"get server certificate from an ssl connection\"\"\"\n    logger.debug(\"Helper.servercert_get(%s:%s)\", hostname, port)\n\n    pem_cert = None\n\n    if ipv6_chk(logger, hostname):\n        sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)\n    else:\n        sock = socks.socksocket()\n\n    # backup - set sni to hostname\n    if not sni:\n        sni = hostname\n\n    context = ssl.create_default_context()  # NOSONAR\n    context.check_hostname = False\n    context.verify_mode = ssl.CERT_NONE  # NOSONAR\n    context.options |= ssl.PROTOCOL_TLS_CLIENT\n    context.set_alpn_protocols([\"acme-tls/1\"])\n    # reject insecure ssl version\n    try:\n        # this does not work on RH8\n        context.minimum_version = ssl.TLSVersion.TLSv1_2\n    except Exception:  # pragma: no cover\n        logger.error(\n            \"Error while getting the peer certifiate: minimum tls version not supported\"\n        )\n\n    context.options |= ssl.PROTOCOL_TLS_SERVER\n\n    if proxy_server:\n        (proxy_proto, proxy_addr, proxy_port) = proxystring_convert(\n            logger, proxy_server\n        )\n        if proxy_proto and proxy_addr and proxy_port:\n            logger.debug(\"servercert_get(): configure proxy\")\n            sock.setproxy(proxy_proto, proxy_addr, port=proxy_port)\n    try:\n        sock.connect((hostname, port))\n        with context.wrap_socket(sock, server_hostname=sni) as sslsock:\n            logger.debug(\n                \"servercert_get(): %s:%s:%s version: %s\",\n                hostname,\n                sni,\n                port,\n                sslsock.version(),\n            )\n            der_cert = sslsock.getpeercert(True)\n            # from binary DER format to PEM\n            if der_cert:\n                pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)\n    except Exception as err_:\n        logger.error(\"Could not get peer certificate. Error: %s\", err_)\n        pem_cert = None\n\n    if pem_cert:\n        logger.debug(\n            \"Helper.servercert_get() ended with: %s\",\n            b64_encode(logger, convert_string_to_byte(pem_cert)),\n        )\n    else:\n        logger.debug(\"Helper.servercert_get() ended with: None\")\n    return pem_cert\n\n\ndef v6_adjust(logger: logging.Logger, url: str) -> Tuple[Dict[str, str], str]:\n    \"\"\"corner case for v6 addresses\"\"\"\n    logger.debug(\"Helper.v6_adjust(%s)\", url)\n\n    headers = {\n        \"Connection\": \"close\",\n        \"Accept-Encoding\": \"gzip\",\n        \"User-Agent\": USER_AGENT,\n    }\n\n    url_dic = parse_url(logger, url)\n\n    # adjust headers and url in case we have an ipv6address\n    if ipv6_chk(logger, url_dic[\"host\"]):\n        headers[\"Host\"] = url_dic[\"host\"]\n        url = f\"{url_dic['proto']}://[{url_dic['host']}]/{url_dic['path']}\"\n\n    logger.debug(\"Helper.v6_adjust() ended\")\n    return (headers, url)\n\n\ndef header_info_get(\n    logger: logging.Logger,\n    csr: str,\n    vlist: List[str] = (\"id\", \"name\", \"header_info\"),\n    field_name: str = \"csr\",\n) -> List[str]:\n    \"\"\"lookup header information\"\"\"\n    logger.debug(\"Helper.header_info_get()\")\n\n    try:\n        from acme_srv.db_handler import DBstore  # pylint: disable=c0415\n\n        dbstore = DBstore(logger=logger)\n        result = dbstore.certificates_search(field_name, csr, vlist)\n    except Exception as err:\n        result = []\n        logger.error(\"Error while getting header_info from database: %s\", err)\n\n    return list(result)\n\n\ndef get_url(environ: Dict[str, str], include_path: bool = False) -> str:\n    \"\"\"get url\"\"\"\n    if \"HTTP_HOST\" in environ:\n        server_name = html.escape(environ[\"HTTP_HOST\"])\n    else:\n        server_name = \"localhost\"\n\n    if \"SERVER_PORT\" in environ:\n        port = html.escape(environ[\"SERVER_PORT\"])\n    else:\n        port = 80\n\n    if \"HTTP_X_FORWARDED_PROTO\" in environ:\n        proto = html.escape(environ[\"HTTP_X_FORWARDED_PROTO\"])\n    elif \"wsgi.url_scheme\" in environ:\n        proto = html.escape(environ[\"wsgi.url_scheme\"])\n    elif int(port) == 443:\n        proto = \"https\"\n    else:\n        proto = \"http\"\n\n    if include_path and \"PATH_INFO\" in environ:\n        result = f'{proto}://{server_name}{html.escape(environ[\"PATH_INFO\"])}'\n    else:\n        result = f\"{proto}://{server_name}\"\n    return result\n\n\ndef parse_url(logger: logging.Logger, url: str) -> Dict[str, str]:\n    \"\"\"split url into pieces\"\"\"\n    logger.debug(\"Helper.parse_url()\")\n\n    url_dic = {\n        \"proto\": urlparse(url).scheme,\n        \"host\": urlparse(url).netloc,\n        \"path\": urlparse(url).path,\n    }\n    return url_dic\n\n\ndef encode_url(logger: logging.Logger, input_string: str) -> str:\n    \"\"\"urlencoding\"\"\"\n    logger.debug(\"Helper.encode_url(%s)\", input_string)\n\n    return quote(input_string)\n\n\ndef request_operation(\n    logger: logging.Logger,\n    headers: Dict[str, str] = None,\n    proxy: Dict[str, str] = None,\n    timeout: int = 20,\n    url: str = None,\n    session=requests,\n    method: str = \"GET\",\n    payload: Dict[str, str] = None,\n    verify: bool = True,\n):\n    \"\"\"check if a for a string value taken from profile if its a variable inside a class and apply value\"\"\"\n    logger.debug(\"Helper.api_operation(): method: %s\", method)\n\n    try:\n        if method.lower() == \"get\":\n            api_response = session.get(\n                url=url, headers=headers, proxies=proxy, timeout=timeout, verify=verify\n            )\n        elif method.lower() == \"post\":\n            api_response = session.post(\n                url=url,\n                headers=headers,\n                proxies=proxy,\n                timeout=timeout,\n                json=payload,\n                verify=verify,\n            )\n        elif method.lower() == \"put\":\n            api_response = session.put(\n                url=url,\n                headers=headers,\n                proxies=proxy,\n                timeout=timeout,\n                json=payload,\n                verify=verify,\n            )\n        else:\n            logger.error(\"Unknown request method: %s\", method)\n            api_response = None\n\n        code = api_response.status_code\n        if api_response.text:\n            try:\n                content = api_response.json()\n            except Exception as err_:\n                logger.error(\n                    \"Request_operation returned error during json parsing: %s\", err_\n                )\n                content = str(err_)\n        else:\n            content = None\n\n    except Exception as err_:\n        logger.error(\"Request_operation returned error: %s\", err_)\n        code = 500\n        content = str(err_)\n\n    logger.debug(\"Helper.request_operation() ended with: %s\", code)\n    return code, content\n"
  },
  {
    "path": "acme_srv/helpers/plugin_loader.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Plugin loading utilities for acme2certifier\"\"\"\nimport importlib\nimport importlib.util\nimport logging\nfrom typing import Dict\n\n\ndef ca_handler_load(\n    logger: logging.Logger, config_dic: Dict\n) -> importlib.import_module:\n    \"\"\"load and return ca_handler\"\"\"\n    logger.debug(\"Helper.ca_handler_load()\")\n\n    if \"CAhandler\" not in config_dic:\n        logger.error(\"CAhandler configuration missing in config file\")\n        return None\n\n    if \"handler_file\" in config_dic[\"CAhandler\"]:\n        # try to load handler from file\n        try:\n            spec = importlib.util.spec_from_file_location(\n                \"CAhandler\", config_dic[\"CAhandler\"][\"handler_file\"]\n            )\n            ca_handler_module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(ca_handler_module)\n            return ca_handler_module\n        except Exception as err_:\n            logger.critical(\n                \"Loading CAhandler configured in cfg failed with err: %s\", err_\n            )\n\n    # if no 'handler_file' provided or loading was unsuccessful, try to load default handler\n    try:\n        ca_handler_module = importlib.import_module(\"acme_srv.ca_handler\")\n    except Exception as err_:\n        logger.critical(\"Loading default CAhandler failed with err: %s\", err_)\n        ca_handler_module = None\n\n    return ca_handler_module\n\n\ndef eab_handler_load(\n    logger: logging.Logger, config_dic: Dict\n) -> importlib.import_module:\n    \"\"\"load and return eab_handler\"\"\"\n    logger.debug(\"Helper.eab_handler_load()\")\n    # pylint: disable=w0621\n    if \"EABhandler\" in config_dic and \"eab_handler_file\" in config_dic[\"EABhandler\"]:\n        # try to load handler from file\n        try:\n            spec = importlib.util.spec_from_file_location(\n                \"EABhandler\", config_dic[\"EABhandler\"][\"eab_handler_file\"]\n            )\n            eab_handler_module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(eab_handler_module)\n        except Exception as err_:\n            logger.critical(\n                \"Loading EABhandler configured in cfg failed with err: %s\", err_\n            )\n            try:\n                eab_handler_module = importlib.import_module(\"acme_srv.eab_handler\")\n            except Exception as err_:\n                eab_handler_module = None\n                logger.critical(\"Loading default EABhandler failed with err: %s\", err_)\n    else:\n        if \"EABhandler\" in config_dic:\n            try:\n                eab_handler_module = importlib.import_module(\"acme_srv.eab_handler\")\n            except Exception as err_:\n                logger.critical(\"Loading default EABhandler failed with err: %s\", err_)\n                eab_handler_module = None\n        else:\n            logger.error(\"EABhandler configuration missing in config file\")\n            eab_handler_module = None\n\n    return eab_handler_module\n\n\ndef hooks_load(logger: logging.Logger, config_dic: Dict) -> importlib.import_module:\n    \"\"\"load and return hooks\"\"\"\n    logger.debug(\"Helper.hooks_load()\")\n\n    hooks_module = None\n    if \"Hooks\" in config_dic and \"hooks_file\" in config_dic[\"Hooks\"]:\n        # try to load hooks from file\n        try:\n            spec = importlib.util.spec_from_file_location(\n                \"Hooks\", config_dic[\"Hooks\"][\"hooks_file\"]\n            )\n            hooks_module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(hooks_module)\n        except Exception as err_:\n            logger.critical(\n                \"Loading Hooks configured in cfg failed with err: %s\",\n                err_,\n            )\n\n    return hooks_module\n"
  },
  {
    "path": "acme_srv/helpers/utils.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"General utilities for acme2certifier\"\"\"\nimport random\nimport logging\nfrom typing import Dict, List\nfrom .global_variables import PARSING_ERR_MSG\n\n\ndef error_dic_get(logger: logging.Logger) -> Dict[str, str]:\n    \"\"\"load acme error messages\"\"\"\n    logger.debug(\"Helper.error_dict_get()\")\n    # this is the main dictionary\n    error_dic = {\n        \"accountdoesnotexist\": \"urn:ietf:params:acme:error:accountDoesNotExist\",\n        \"alreadyrevoked\": \"urn:ietf:params:acme:error:alreadyRevoked\",\n        \"badcsr\": \"urn:ietf:params:acme:error:badCSR\",\n        \"badpubkey\": \"urn:ietf:params:acme:error:badPublicKey\",\n        \"badrevocationreason\": \"urn:ietf:params:acme:error:badRevocationReason\",\n        \"externalaccountrequired\": \"urn:ietf:params:acme:error:externalAccountRequired\",\n        \"invalidcontact\": \"urn:ietf:params:acme:error:invalidContact\",\n        \"invalidprofile\": \"urn:ietf:params:acme:error:invalidProfile\",\n        \"malformed\": \"urn:ietf:params:acme:error:malformed\",\n        \"ordernotready\": \"urn:ietf:params:acme:error:orderNotReady\",\n        \"ratelimited\": \"urn:ietf:params:acme:error:rateLimited\",\n        \"rejectedidentifier\": \"urn:ietf:params:acme:error:rejectedIdentifier\",\n        \"serverinternal\": \"urn:ietf:params:acme:error:serverInternal\",\n        \"unauthorized\": \"urn:ietf:params:acme:error:unauthorized\",\n        \"unsupportedidentifier\": \"urn:ietf:params:acme:error:unsupportedIdentifier\",\n        \"useractionrequired\": \"urn:ietf:params:acme:error:userActionRequired\",\n    }\n    return error_dic\n\n\ndef enrollment_config_log(\n    logger: logging.Logger, obj: object, handler_skiplist: List[str] = None\n):\n    \"\"\"log enrollment configuration\"\"\"\n    logger.debug(\"Helper.enrollment_config_log()\")\n\n    skiplist = [\n        \"logger\",\n        \"session\",\n        \"password\",\n        \"api_key\",\n        \"api_password\",\n        \"key\",\n        \"secret\",\n        \"token\",\n        \"err_msg_dic\",\n        \"dbstore\",\n    ]\n\n    if handler_skiplist and isinstance(handler_skiplist, list):\n        skiplist.extend(handler_skiplist)\n\n    if handler_skiplist and PARSING_ERR_MSG in handler_skiplist:\n        logger.error(\n            \"Enrollment configuration won't get logged due to a configuration error.\"\n        )\n    else:\n        enroll_parameter_list = []\n        for key, value in obj.__dict__.items():\n            if key.startswith(\"__\") or key in skiplist:\n                continue\n            enroll_parameter_list.append(f\"{key}: {value}\")\n        logger.info(\"Enrollment configuration: %s\", enroll_parameter_list)\n\n\ndef radomize_parameter_list(\n    logger: logging.Logger, ca_handler: object, parameter_list: List[str] = None\n):\n    \"\"\"randomize parameter list\"\"\"\n    logger.debug(\"Helper.radomize_parameter_list()\")\n\n    tmp_dic = {}\n    for parameter in parameter_list:\n        if hasattr(ca_handler, parameter):\n            value = getattr(ca_handler, parameter)\n            if value and \",\" in value:\n                values_list = value.split(\",\")\n                tmp_dic[parameter] = []\n                for ele in values_list:\n                    tmp_dic[parameter].append(ele.strip())\n\n    if tmp_dic:\n        # Find the list with the minimum length in tmp_dic values\n        min_length_list = min(tmp_dic.values(), key=len)\n        # Get the length of that list\n        min_len = len(min_length_list)\n\n        # Calculate random number as index for the parameter list\n        index = random.randint(0, min_len - 1)\n        # set parameter values\n        for parameter, value_list in tmp_dic.items():\n            setattr(ca_handler, parameter, value_list[index])\n\n\ndef handler_config_check(logger, handler, parameterlist) -> str:\n    \"\"\"check if handler config is valid\"\"\"\n    logger.debug(\"Helper.handler_config_check()\")\n    error = None\n\n    error = None\n    for ele in parameterlist:\n        if not getattr(handler, ele):\n            error = f\"{ele} parameter is missing in config file\"\n            logger.error(\"Configuration check ended with error: %s\", error)\n            break\n\n    logger.debug(\"Helper.handler_config_check() ended with %s\", error)\n    return error\n"
  },
  {
    "path": "acme_srv/helpers/validation.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Validation utilities for acme2certifier\"\"\"\nimport re\nimport logging\nimport ipaddress\nfrom typing import List, Dict, Tuple\n\n\ndef dkeys_lower(tree: Dict[str, str]) -> Dict[str, str]:\n    \"\"\"lower characters in payload string\"\"\"\n    if isinstance(tree, dict):\n        result = {k.lower(): dkeys_lower(v) for k, v in tree.items()}\n    elif isinstance(tree, list):\n        result = [dkeys_lower(ele) for ele in tree]\n    else:\n        result = tree\n    return result\n\n\ndef fqdn_in_san_check(logger: logging.Logger, san_list: List[str], fqdn: str) -> bool:\n    \"\"\"check if fqdn is in a list of sans\"\"\"\n    logger.debug(\"Helper.fqdn_in_san_check([%s], %s)\", san_list, fqdn)\n\n    result = False\n    if fqdn and san_list:\n        for san in san_list:\n            try:\n                (_type, value) = san.lower().split(\":\", 1)\n                if fqdn == value:\n                    result = True\n                    break\n            except Exception:\n                logger.error(\"Error during SAN check. SAN split failed: %s\", san)\n\n    logger.debug(\"Helper.fqdn_in_san_check() ended with: %s\", result)\n    return result\n\n\ndef validate_csr(logger: logging.Logger, order_dic: Dict[str, str], _csr) -> bool:\n    \"\"\"validate certificate signing request against order\"\"\"\n    logger.debug(\"Helper.validate_csr(%s)\", order_dic)\n    return True\n\n\ndef validate_email(logger: logging.Logger, contact_list: List[str]) -> bool:\n    \"\"\"validate contact against RFC608\"\"\"\n    logger.debug(\"Helper.validate_email()\")\n    result = True\n    pattern = r\"^[A-Za-z0-9\\.\\+_-]+@[A-Za-z]+[A-Za-z0-9\\._-]+[A-Za-z0-9]+\\.[a-zA-Z\\.]+[a-zA-Z]+$\"\n    # check if we got a list or single address\n    if isinstance(contact_list, list):\n        for contact in contact_list:\n            contact = contact.replace(\"mailto:\", \"\")\n            contact = contact.lstrip()\n            tmp_result = bool(re.search(pattern, contact))\n            logger.debug(\"# validate: %s result: %s\", contact, tmp_result)\n            if not tmp_result:\n                result = tmp_result\n    else:\n        contact_list = contact_list.replace(\"mailto:\", \"\")\n        contact_list = contact_list.lstrip()\n        result = bool(re.search(pattern, contact_list))\n        logger.debug(\n            \"Helper.validate_email() of: %s emded with result: %s\", contact_list, result\n        )\n    return result\n\n\ndef validate_identifier(\n    logger: logging.Logger,\n    id_type: str,\n    identifier: str,\n    tnauthlist_support: bool = False,\n) -> bool:\n    \"\"\"validate identifier format\"\"\"\n    logger.debug(\"Helper.validate_identifier()\")\n\n    result = False\n    if identifier:\n        if id_type == \"dns\":\n            result = validate_fqdn(logger, identifier)\n        elif id_type == \"ip\":\n            result = validate_ip(logger, identifier)\n        elif id_type == \"email\":\n            result = validate_email(logger, [identifier])\n        elif id_type == \"tnauthlist\" and tnauthlist_support:\n            result = True\n\n    logger.debug(\"Helper.validate_identifier() ended with: %s\", result)\n    return result\n\n\ndef validate_ip(logger: logging.Logger, ip: str) -> bool:\n    \"\"\"validate ip address\"\"\"\n    logger.debug(\"Helper.validate_ip()\")\n    try:\n        ipaddress.ip_address(ip)\n        result = True\n    except ValueError:\n        result = False\n    logger.debug(\"Helper.validate_ip() ended with: %s\", result)\n    return result\n\n\ndef validate_fqdn(logger: logging.Logger, fqdn: str) -> bool:\n    \"\"\"validate fqdn\"\"\"\n    logger.debug(\"Helper.validate_fqdn()\")\n\n    result = False\n    regex = r\"^(([a-z0-9]\\-*[a-z0-9]*){1,63}\\.?){1,255}$\"\n    p = re.compile(regex)\n    if re.search(p, fqdn):\n        result = True\n\n    if not result:\n        logger.debug(\n            \"Helper.validate_fqdn(): invalid fqdn. Check for wildcard : %s\", fqdn\n        )\n        regex = r\"^\\*\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\"\n        p = re.compile(regex)\n        if re.search(p, fqdn):\n            result = True\n\n    logger.debug(\"Helper.validate_fqdn() ended with: %s\", result)\n    return result\n\n\ndef ip_validate(logger: logging.Logger, ip_addr: str) -> Tuple[str, bool]:\n    \"\"\"validate ip address\"\"\"\n    logger.debug(\"Helper.ip_validate(%s)\", ip_addr)\n\n    try:\n        reverse_pointer = ipaddress.ip_address(ip_addr).reverse_pointer\n        invalid = False\n    except ValueError:\n        reverse_pointer = None\n        invalid = True\n    logger.debug(\"Helper.ip_validate() ended with: %s:%s\", reverse_pointer, invalid)\n    return (reverse_pointer, invalid)\n\n\ndef ipv6_chk(logger: logging.Logger, address: str) -> bool:\n    \"\"\"check if an address is ipv6\"\"\"\n    logger.debug(\"Helper.ipv6_chk(%s)\", address)\n\n    try:\n        # we need to set a host header and braces for ipv6 headers and\n        if isinstance(ipaddress.ip_address(address), ipaddress.IPv6Address):\n            logger.debug(\"Helper.v6_adjust(}): ipv6 address detected\")\n            result = True\n        else:\n            result = False\n    except Exception:\n        result = False\n\n    logger.debug(\"Helper.ipv6_chk() ended with %s\", result)\n    return result\n\n\ndef cn_validate(logger: logging.Logger, cn: str) -> bool:\n    \"\"\"validate common name\"\"\"\n    logger.debug(\"Helper.cn_validate(%s)\", cn)\n\n    error = False\n    if cn:\n        # check if CN is a valid IP address\n        result = validate_ip(logger, cn)\n        if not result:\n            # check if CN is a valid fqdn\n            result = validate_fqdn(logger, cn)\n        if not result:\n            error = \"Profile subject check failed: CN validation failed\"\n    else:\n        error = \"Profile subject check failed: commonName missing\"\n\n    logger.debug(\"Helper.cn_validate() ended with: %s\", error)\n    return error\n"
  },
  {
    "path": "acme_srv/housekeeping.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Housekeeping class\"\"\"\nfrom __future__ import print_function\nimport csv\nimport json\nfrom typing import List, Tuple, Dict\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.authorization import Authorization\nfrom acme_srv.certificate import Certificate\nfrom acme_srv.message import Message\nfrom acme_srv.order import Order\nfrom acme_srv.helper import (\n    load_config,\n    uts_to_date_utc,\n    cert_dates_get,\n    cert_serial_get,\n    uts_now,\n    error_dic_get,\n)\nfrom acme_srv.version import __version__\n\n\nclass Housekeeping(object):\n    \"\"\"Housekeeping class\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.dbstore = DBstore(debug, self.logger)\n        self.message = Message(debug, None, self.logger)\n        self.error_msg_dic = error_dic_get(self.logger)\n        self.debug = debug\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _accountlist_get(self) -> Dict[str, str]:\n        \"\"\"get list of certs from database\"\"\"\n        self.logger.debug(\"Housekeeping._certlist_get()\")\n        try:\n            result = self.dbstore.accountlist_get()\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to retrieve account list: %s\",\n                err_,\n            )\n            result = None\n        return result\n\n    def _certificatelist_get(self) -> Dict[str, str]:\n        \"\"\"get list of certs from database\"\"\"\n        self.logger.debug(\"Housekeeping._certlist_get()\")\n        try:\n            result = self.dbstore.certificatelist_get()\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to retrieve certificate list: %s\",\n                err_,\n            )\n            result = None\n        return result\n\n    def _cliconfig_check(self, config_dic: Dict[str, str]) -> bool:\n        \"\"\"verify config\"\"\"\n        self.logger.debug(\"config_check()\")\n\n        check_result = True\n        if (\n            \"list\" not in config_dic\n            and \"jwkname\" not in config_dic\n            and \"jwk\" not in config_dic\n        ):\n            self.logger.error(\n                \"Error: cliuser_mgmt.py config_check() failed: Either jwkname or jwk must be specified\"\n            )\n            check_result = False\n\n        return check_result\n\n    def _cliaccounts_list(self, silent: bool = True) -> Dict[str, str]:\n        \"\"\"list cli accounts\"\"\"\n        self.logger.debug(\"Housekeeping._cliaccounts_list()\")\n        try:\n            result = self.dbstore.cliaccountlist_get()\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to retrieve CLI account list: %s\",\n                err_,\n            )\n            result = None\n        if result and not silent:\n            self._cliaccounts_format(result)\n        return result\n\n    def _cliaccounts_format(self, result_list: List[str]):\n        \"\"\"format cliaccount report\"\"\"\n        self.logger.debug(\"Housekeeping._cliaccounts_format()\")\n        try:\n            print(\n                f\"\\n{'Name'.ljust(15)}|{'Contact'.ljust(20)}|{'cliadm'.ljust(6)}|{'repadm'.ljust(6)}|{'certadm'.ljust(7)}|{'Created at'.ljust(20)}\"\n            )\n            print(\"-\" * 78)\n            for account in sorted(result_list, key=lambda k: k[\"id\"]):\n                print(\n                    f\"{account['name'][:15].ljust(15)}|{account['contact'][:20].ljust(20)}|{str(bool(account['cliadmin'])).ljust(6)}|{str(bool(account['reportadmin'])).ljust(6)}|{str(bool(account['certificateadmin'])).ljust(7)}|{account['created_at'].ljust(20)}\"\n                )\n            print(\"\\n\")\n        except Exception as err:\n            self.logger.error(\"Error in when formating cliaccounts: %s\", err)\n\n    def _report_get(\n        self, payload: Dict[str, str]\n    ) -> Tuple[Dict[str, str], int, str, str]:\n        \"\"\"create report\"\"\"\n        self.logger.debug(\"Housekeeping._report_get()\")\n\n        message = None\n        detail = None\n        response_dic = {}\n\n        if \"name\" in payload[\"data\"] and payload[\"data\"][\"name\"] in (\n            \"certificates\",\n            \"accounts\",\n        ):\n            if \"format\" in payload[\"data\"] and payload[\"data\"][\"format\"] in (\n                \"csv\",\n                \"json\",\n            ):\n                if payload[\"data\"][\"name\"] == \"certificates\":\n                    response_dic[\"data\"] = self.certreport_get(\n                        report_format=payload[\"data\"][\"format\"]\n                    )\n                elif payload[\"data\"][\"name\"] == \"accounts\":\n                    response_dic[\"data\"] = self.accountreport_get(\n                        report_format=payload[\"data\"][\"format\"]\n                    )\n                code = 200\n            else:\n                code = 400\n                message = self.error_msg_dic[\"malformed\"]\n                detail = \"unknown report format\"\n        else:\n            code = 400\n            message = self.error_msg_dic[\"malformed\"]\n            detail = \"unknown report type\"\n\n        self.logger.debug(\"Housekeeping._report_get() ended\")\n        return (response_dic, code, message, detail)\n\n    def _clireport_get(\n        self, payload: Dict[str, str], permissions_dic: Dict[str, str]\n    ) -> Tuple[int, str, str, Dict[str, str]]:\n        \"\"\"get reports for CLI\"\"\"\n        self.logger.debug(\"Housekeeping._clireport_get()\")\n\n        response_dic = {}\n        message = None\n        detail = None\n\n        if \"reportadmin\" in permissions_dic and permissions_dic[\"reportadmin\"]:\n            # create reports as we have permissions to do so\n            (response_dic, code, message, detail) = self._report_get(payload)\n        else:\n            code = 403\n            message = self.error_msg_dic[\"unauthorized\"]\n            detail = \"No permissions to download reports\"\n\n        self.logger.debug(\n            \"Housekeeping._clireport_get() returned with: %s/%s\", code, detail\n        )\n        return (code, message, detail, response_dic)\n\n    def _config_load(self):\n        \"\"\"load config from file\"\"\"\n        self.logger.debug(\"Housekeeping._config_load()\")\n        config_dic = load_config()\n        if \"Housekeeping\" in config_dic:\n            self.logger.debug(\"Housekeeping._config_load()\")\n\n    def _uts_fields_set(\n        self,\n        cert: Dict[str, str],\n        cert_raw_field: str,\n        cert_issue_date_field: 0,\n        cert_expire_date_field: 0,\n    ) -> Dict[str, str]:\n        \"\"\"set uts to 0 if we do not have them in dictionary\"\"\"\n        self.logger.debug(\"Housekeeping._zero_uts_fields()\")\n\n        if cert_issue_date_field not in cert or cert_expire_date_field not in cert:\n            cert[cert_issue_date_field] = 0\n            cert[cert_expire_date_field] = 0\n\n        # if uts is zero we try to get the dates from certificate\n        if cert[cert_issue_date_field] == 0 or cert[cert_expire_date_field] == 0:\n            # cover cases without certificate in dict\n            if cert_raw_field in cert:\n                (issue_uts, expire_uts) = cert_dates_get(\n                    self.logger, cert[cert_raw_field]\n                )\n                cert[cert_issue_date_field] = issue_uts\n                cert[cert_expire_date_field] = expire_uts\n            else:\n                cert[cert_issue_date_field] = 0\n                cert[cert_expire_date_field] = 0\n\n        self.logger.debug(\"Housekeeping._uts_fields_set() ended.\")\n        return cert\n\n    def _cert_serial_add(self, cert_raw: str) -> str:\n        \"\"\"add serial number form cert\"\"\"\n        self.logger.debug(\"Housekeeping._cert_serial_add()\")\n\n        try:\n            serial = cert_serial_get(self.logger, cert_raw)\n        except Exception:\n            serial = \"\"\n\n        self.logger.debug(\"Housekeeping._cert_serial_add() ended\")\n        return serial\n\n    def _convert_data(self, cert_list: List[str]) -> List[str]:\n        \"\"\"convert data from uts to real date\"\"\"\n        self.logger.debug(\"Housekeeping._convert_dates()\")\n\n        cert_serial_field = \"certificate.serial\"\n        cert_issue_date_field = \"certificate.issue_uts\"\n        cert_issue_dateh_field = \"certificate.issue_date\"\n        cert_expire_date_field = \"certificate.expire_uts\"\n        cert_expire_dateh_field = \"certificate.expire_date\"\n        cert_raw_field = \"certificate.cert_raw\"\n        date_format = \"%Y-%m-%d %H:%M:%S\"\n\n        for cert in cert_list:\n            expire_list = (\n                \"order.expires\",\n                \"authorization.expires\",\n                \"challenge.expires\",\n            )\n            for ele in expire_list:\n                if ele in cert and cert[ele]:\n                    cert[ele] = uts_to_date_utc(cert[ele], date_format)\n\n            # set timestamps for issue and expiry dates\n            cert = self._uts_fields_set(\n                cert, cert_raw_field, cert_issue_date_field, cert_expire_date_field\n            )\n\n            if cert[cert_issue_date_field] > 0 and cert[cert_expire_date_field] > 0:\n                cert[cert_issue_dateh_field] = uts_to_date_utc(\n                    cert[cert_issue_date_field], date_format\n                )\n                cert[cert_expire_dateh_field] = uts_to_date_utc(\n                    cert[cert_expire_date_field], date_format\n                )\n            else:\n                cert[cert_issue_dateh_field] = \"\"\n                cert[cert_expire_dateh_field] = \"\"\n\n            # add serial number\n            if cert_raw_field in cert:\n                cert[cert_serial_field] = self._cert_serial_add(cert[cert_raw_field])\n\n        return cert_list\n\n    def _csv_dump(self, filename: str, content: List[str]):\n        \"\"\"dump content csv file\"\"\"\n        self.logger.debug(\"Housekeeping._csv_dump()\")\n        with open(filename, \"w\", encoding=\"utf8\", newline=\"\") as file_:\n            writer = csv.writer(\n                file_, delimiter=\",\", quotechar='\"', quoting=csv.QUOTE_NONNUMERIC\n            )\n            writer.writerows(content)\n\n    def _data_dic_create(self, config_dic: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"create dictionalry\"\"\"\n        self.logger.debug(\"Housekeeping._data_dic_create()\")\n\n        data_dic = {}\n        if \"jwkname\" in config_dic:\n            data_dic[\"name\"] = config_dic[\"jwkname\"]\n        else:\n            if \"jwk\" in config_dic and \"kid\" in config_dic[\"jwk\"]:\n                data_dic[\"name\"] = config_dic[\"jwk\"][\"kid\"]\n\n        self.logger.debug(\"Housekeeping._data_dic_create() ended\")\n        return data_dic\n\n    def _data_dic_build(self, config_dic: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"cli user manager\"\"\"\n        self.logger.debug(\"Housekeeping._data_dic_build()\")\n\n        data_dic = self._data_dic_create(config_dic)\n        if \"delete\" not in config_dic or not config_dic[\"delete\"]:\n\n            if \"permissions\" in config_dic:\n                try:\n                    data_dic.update(config_dic[\"permissions\"])\n                except Exception as err:\n                    self.logger.error(\n                        \"Error in while building the data dictionary: %s\",\n                        err,\n                    )\n\n            if \"jwk\" in config_dic:\n                data_dic[\"jwk\"] = json.dumps(config_dic[\"jwk\"])\n\n            if \"email\" in config_dic:\n                data_dic[\"contact\"] = config_dic[\"email\"]\n\n        self.logger.debug(\"Housekeeping._data_dic_build() ended\")\n        return data_dic\n\n    def _json_dump(self, filename: str, data_: Dict[str, str]):\n        \"\"\"dump content json file\"\"\"\n        self.logger.debug(\"Housekeeping._json_dump()\")\n        jdump = json.dumps(data_, ensure_ascii=False, indent=4, default=str)\n        with open(filename, \"w\", encoding=\"utf8\", newline=\"\") as file_:\n            file_.write(jdump)  # lgtm [py/clear-text-storage-sensitive-data]\n\n    def _fieldlist_normalize(\n        self, field_list: List[str], prefix: str\n    ) -> Dict[str, str]:\n        \"\"\"normalize field_list\"\"\"\n        self.logger.debug(\"Housekeeping._fieldlist_normalize()\")\n        field_dic = {}\n        for field in field_list:\n            f_list = field.split(\"__\")\n            # items from selected list which do not have a table reference get prefix added\n            if len(f_list) == 1:\n                new_field = f\"{prefix}.{field}\"\n            elif f_list[-2] == \"status\" and len(f_list) >= 3:\n                # status fields have one reference more\n                new_field = f\"{f_list[-3]}.{f_list[-2]}.{f_list[-1]}\"\n            else:\n                new_field = f\"{f_list[-2]}.{f_list[-1]}\"\n            field_dic[field] = new_field\n\n        return field_dic\n\n    def _lists_normalize(\n        self, field_list: List[str], value_list: List[str], prefix: str\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"normalize list\"\"\"\n        self.logger.debug(\"Housekeeping._list_normalize()\")\n\n        field_dic = self._fieldlist_normalize(field_list, prefix)\n\n        new_list = []\n        for v_list in value_list:\n            # create a temporary dictionary wiht the renamed fields\n            tmp_dic = {}\n            for field in v_list:\n                if field in field_dic:\n                    tmp_dic[field_dic[field]] = v_list[field]\n            # append dicutionary to list\n            new_list.append(tmp_dic)\n\n        # get field_list\n        field_list = list(field_dic.values())\n\n        return field_list, new_list\n\n    def _account_list_convert(self, tmp_json: List[str]) -> List[str]:\n        \"\"\"create account list\"\"\"\n        self.logger.debug(\"Housekeeping._account_list_convert()\")\n\n        account_list = []\n        for account in tmp_json:\n            tmp_json[account][\"orders\"] = []\n            for order in tmp_json[account][\"orders_dic\"]:\n                tmp_json[account][\"orders_dic\"][order][\"authorizations\"] = []\n                for authorization in tmp_json[account][\"orders_dic\"][order][\n                    \"authorizations_dic\"\n                ]:\n                    tmp_json[account][\"orders_dic\"][order][\"authorizations_dic\"][\n                        authorization\n                    ][\"challenges\"] = []\n                    # build list from challenges and delete dictionary\n                    for _name, challenge in tmp_json[account][\"orders_dic\"][order][\n                        \"authorizations_dic\"\n                    ][authorization][\"challenges_dic\"].items():\n                        tmp_json[account][\"orders_dic\"][order][\"authorizations_dic\"][\n                            authorization\n                        ][\"challenges\"].append(challenge)\n                    del tmp_json[account][\"orders_dic\"][order][\"authorizations_dic\"][\n                        authorization\n                    ][\"challenges_dic\"]\n                    # build list from authorizations\n                    tmp_json[account][\"orders_dic\"][order][\"authorizations\"].append(\n                        tmp_json[account][\"orders_dic\"][order][\"authorizations_dic\"][\n                            authorization\n                        ]\n                    )\n                # delete authorization dictionary\n                del tmp_json[account][\"orders_dic\"][order][\"authorizations_dic\"]\n                # build list of orders\n                tmp_json[account][\"orders\"].append(\n                    tmp_json[account][\"orders_dic\"][order]\n                )\n            del tmp_json[account][\"orders_dic\"]\n\n            # add entry to output list\n            account_list.append(tmp_json[account])\n\n        self.logger.debug(\"Housekeeping._account_list_convert() ended\")\n        return account_list\n\n    def _dicstructure_create(\n        self,\n        tmp_json: Dict[str, str],\n        ele: str,\n        account_field: str,\n        order_field: str,\n        authz_field: str,\n        chall_field: str,\n    ) -> Dict[str, str]:\n        # pylint: disable=r0913\n        \"\"\"create dictionary structure\"\"\"\n        self.logger.debug(\"Housekeeping._dicstructure_create()\")\n\n        # create account entry in case it does not exist\n        if ele[account_field] not in tmp_json:\n            tmp_json[ele[account_field]] = {}\n            tmp_json[ele[account_field]][\"orders_dic\"] = {}\n\n        if ele[order_field] not in tmp_json[ele[account_field]][\"orders_dic\"]:\n            tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]] = {}\n            tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ] = {}\n\n        if (\n            ele[authz_field]\n            not in tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ]\n        ):\n            tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ][ele[authz_field]] = {}\n            tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ][ele[authz_field]][\"challenges_dic\"] = {}\n\n        if (\n            ele[chall_field]\n            not in tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ][ele[authz_field]][\"challenges_dic\"]\n        ):\n            tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                \"authorizations_dic\"\n            ][ele[authz_field]][\"challenges_dic\"][ele[chall_field]] = {}\n\n        self.logger.debug(\"Housekeeping._dicstructure_create() ended\")\n        return tmp_json\n\n    def _account_dic_create(\n        self, account_list: List[str]\n    ) -> Tuple[Dict[str, str], List[str]]:\n        \"\"\"account list create\"\"\"\n        self.logger.debug(\"Housekeeping._account_dic_create()\")\n\n        account_field = \"account.name\"\n        order_field = \"order.name\"\n        authz_field = \"authorization.name\"\n        chall_field = \"challenge.name\"\n\n        tmp_json = {}\n        error_list = []\n\n        for ele in account_list:\n\n            # we have to ensure that all keys we need to nest are in\n            if ele.keys() >= {account_field, order_field, authz_field, chall_field}:\n\n                # create dictionary structure (if needed)\n                tmp_json = self._dicstructure_create(\n                    tmp_json, ele, account_field, order_field, authz_field, chall_field\n                )\n\n                # dump data in\n                for value in ele:\n                    if value.startswith(\"account.\"):\n                        tmp_json[ele[account_field]][value] = ele[value]\n                    elif value.startswith(\"order.\"):\n                        tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                            value\n                        ] = ele[value]\n                    elif value.startswith(\"authorization.\"):\n                        tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                            \"authorizations_dic\"\n                        ][ele[authz_field]][value] = ele[value]\n                    elif value.startswith(\"challenge\"):\n                        tmp_json[ele[account_field]][\"orders_dic\"][ele[order_field]][\n                            \"authorizations_dic\"\n                        ][ele[authz_field]][\"challenges_dic\"][ele[chall_field]][\n                            value\n                        ] = ele[\n                            value\n                        ]\n\n            else:\n                error_list.append(ele)\n\n        self.logger.debug(\"Housekeeping._account_dic_create() ended\")\n        return (tmp_json, error_list)\n\n    def _to_acc_json(self, account_list: List[str]) -> List[str]:\n        \"\"\"stack list to json\"\"\"\n        self.logger.debug(\"Housekeeping._to_acc_json()\")\n\n        # create main dictionary and errorlist\n        (tmp_json, error_list) = self._account_dic_create(account_list)\n\n        # convert nested dictionaries (challenges, authorizations and orders) into list\n        account_list = self._account_list_convert(tmp_json)\n\n        # add errors\n        if error_list:\n            account_list.append({\"error_list\": error_list})\n\n        return account_list\n\n    def _to_list(self, field_list: List[str], cert_list: List[str]) -> List[str]:\n        \"\"\"convert query to csv format\"\"\"\n        self.logger.debug(\"Housekeeping._to_list()\")\n        csv_list = []\n\n        # attach fieldlist as first row\n        if field_list:\n            csv_list.append(field_list)\n        for cert in cert_list:\n            tmp_list = []\n            # enumarte fields and store them in temporary list\n            for field in field_list:\n                # in case we are missing a field put empty string in\n                if field in cert:\n                    try:\n                        # we need to deal with some errors from past\n                        value = cert[field].replace(\"\\r\\n\", \"\\n\")\n                        value = value.replace(\"\\r\", \"\")\n                        value = value.replace(\"\\n\", \"\")\n                        tmp_list.append(value)\n                    except Exception:\n                        tmp_list.append(cert[field])\n                else:\n                    tmp_list.append(\"\")\n\n            # append list to output\n            csv_list.append(tmp_list)\n        self.logger.debug(\n            \"Housekeeping._to_list() ended with %s entries\", len(csv_list)\n        )\n        return csv_list\n\n    def accountreport_get(\n        self, report_format: str = \"csv\", report_name: str = None, nested: bool = False\n    ) -> List[str]:\n        \"\"\"get account report\"\"\"\n        self.logger.debug(\"Housekeeping.accountreport_get()\")\n        (field_list, account_list) = self._accountlist_get()\n\n        # normalize lists\n        (field_list, account_list) = self._lists_normalize(\n            field_list, account_list, \"account\"\n        )\n\n        # convert dates into human readable format\n        account_list = self._convert_data(account_list)\n\n        if account_list:\n            self.logger.debug(\"output to dump: %s.%s\", report_name, report_format)\n            if report_format == \"csv\":\n                self.logger.debug(\"Housekeeping.certreport_get() dump in csv-format\")\n                csv_list = self._to_list(field_list, account_list)\n                account_list = csv_list\n                if report_name:\n                    self._csv_dump(f\"{report_name}.{report_format}\", csv_list)\n            elif report_format == \"json\":\n                if nested:\n                    account_list = self._to_acc_json(account_list)\n                if report_name:\n                    self._json_dump(f\"{report_name}.{report_format}\", account_list)\n\n        return account_list\n\n    def certreport_get(\n        self, report_format: str = \"csv\", report_name: str = None\n    ) -> List[str]:\n        \"\"\"get certificate report\"\"\"\n        self.logger.debug(\"Housekeeping.certreport_get()\")\n\n        (field_list, cert_list) = self._certificatelist_get()\n\n        # normalize lists\n        (field_list, cert_list) = self._lists_normalize(\n            field_list, cert_list, \"certificate\"\n        )\n\n        # convert dates into human readable format\n        cert_list = self._convert_data(cert_list)\n\n        # extend list by additional fields to have the fileds in output\n        field_list.insert(2, \"certificate.serial\")\n        field_list.insert(7, \"certificate.issue_date\")\n        field_list.insert(8, \"certificate.expire_date\")\n\n        if cert_list:\n            self.logger.debug(\"Prepare output in: %s format\", report_format)\n            if report_format == \"csv\":\n                self.logger.debug(\"Housekeeping.certreport_get(): Dump in csv-format\")\n                csv_list = self._to_list(field_list, cert_list)\n                cert_list = csv_list\n                if report_name:\n                    self._csv_dump(f\"{report_name}.{report_format}\", csv_list)\n            elif report_format == \"json\":\n                self.logger.debug(\"Housekeeping.certreport_get(): Dump in json-format\")\n                if report_name:\n                    self._json_dump(f\"{report_name}.{report_format}\", cert_list)\n            else:\n                self.logger.info(\"No dump just return report\")\n\n        return cert_list\n\n    def certificate_dates_update(self):\n        \"\"\"scan certificates and update issue/expiry date\"\"\"\n        self.logger.debug(\"Housekeeping.certificate_dates_update()\")\n\n        with Certificate(self.debug, None, self.logger) as certificate:\n            certificate.dates_update()\n\n    def certificates_cleanup(\n        self,\n        uts: int = None,\n        purge: bool = False,\n        report_format: str = \"csv\",\n        report_name: str = None,\n    ) -> List[str]:\n        \"\"\"database cleanuip certificate-table\"\"\"\n        self.logger.debug(\"Housekeeping.certificates_cleanup()\")\n        if not uts:\n            uts = uts_now()\n\n        with Certificate(self.debug, None, self.logger) as certificate:\n            (field_list, cert_list) = certificate.cleanup(timestamp=uts, purge=purge)\n\n            # normalize lists\n            # (field_list, cert_list) = self._lists_normalize(field_list, cert_list, 'certificate')\n\n            if report_name:\n                if cert_list:\n                    # dump report to file\n                    if report_format == \"csv\":\n                        self.logger.debug(\n                            \"Housekeeping.certificates_cleanup(): Dump in csv-format\"\n                        )\n                        csv_list = self._to_list(field_list, cert_list)\n                        self._csv_dump(f\"{report_name}.{report_format}\", csv_list)\n                    elif report_format == \"json\":\n                        self.logger.debug(\n                            \"Housekeeping.certificates_cleanup(): Dump in json-format\"\n                        )\n                        self._json_dump(f\"{report_name}.{report_format}\", cert_list)\n                    else:\n                        self.logger.debug(\n                            \"Housekeeping.certificates_cleanup():  No dump just return report\"\n                        )\n                else:\n                    self.logger.debug(\n                        \"Housekeeping.certificates_cleanup(): No certificates to dump\"\n                    )\n\n        return cert_list\n\n    def cli_usermgr(self, config_dic: Dict[str, str]) -> int:\n        \"\"\"cli usermanager\"\"\"\n        self.logger.debug(\"Housekeeping.cli_usermgr()\")\n        check_result = self._cliconfig_check(config_dic)\n\n        # default silence\n        if \"silent\" not in config_dic:\n            config_dic[\"silent\"] = True\n\n        result = None\n        if check_result:\n            data_dic = self._data_dic_build(config_dic)\n            try:\n                if \"name\" in data_dic:\n                    if \"delete\" in config_dic and config_dic[\"delete\"]:\n                        self.dbstore.cliaccount_delete(data_dic)\n                    elif \"list\" in config_dic and config_dic[\"list\"]:\n                        self._cliaccounts_list(silent=config_dic[\"silent\"])\n                    else:\n                        result = self.dbstore.cliaccount_add(data_dic)\n                else:\n                    self.logger.error(\"Error in CLI usermanagement: data incomplete\")\n\n            except Exception as err_:\n                self.logger.critical(\n                    \"Database error: failed to manage CLI user: %s\",\n                    err_,\n                )\n\n        return result\n\n    def authorizations_invalidate(\n        self, uts: int = uts_now(), report_format: str = \"csv\", report_name: str = None\n    ):\n        \"\"\"authorizations cleanup based on expiry date\"\"\"\n        self.logger.debug(\"Housekeeping.authorization_invalidate(%s)\", uts)\n\n        with Authorization(self.debug, None, self.logger) as authorization:\n            # get expired orders\n            (field_list, authorization_list) = authorization.invalidate(timestamp=uts)\n            # normalize lists\n            (field_list, authorization_list) = self._lists_normalize(\n                field_list, authorization_list, \"authorization\"\n            )\n            # convert dates into human readable format\n            authorization_list = self._convert_data(authorization_list)\n\n            if report_name:\n                if authorization_list:\n                    # dump report to file\n                    if report_format == \"csv\":\n                        self.logger.debug(\n                            \"Housekeeping.authorizations_invalidate(): Dump in csv-format\"\n                        )\n                        csv_list = self._to_list(field_list, authorization_list)\n                        self._csv_dump(f\"{report_name}.{report_format}\", csv_list)\n                    elif report_format == \"json\":\n                        self.logger.debug(\n                            \"Housekeeping.authorizations_invalidate(): Dump in json-format\"\n                        )\n                        self._json_dump(\n                            f\"{report_name}.{report_format}\", authorization_list\n                        )\n                    else:\n                        self.logger.debug(\n                            \"Housekeeping.authorizations_invalidate():  No dump just return report\"\n                        )\n                else:\n                    self.logger.debug(\n                        \"Housekeeping.authorizations_invalidate(): No authorizations to dump\"\n                    )\n\n    def dbversion_check(self, version: str = None):\n        \"\"\"check database version\"\"\"\n        self.logger.debug(\"Housekeeping.dbversion_check(%s)\", version)\n\n        if version:\n            try:\n                (result, script_name) = self.dbstore.dbversion_get()\n            except Exception as err_:\n                self.logger.critical(\n                    \"Database error: failed to check database version: %s\",\n                    err_,\n                )\n                result = None\n                script_name = \"handler specific migration\"\n            if result != version:\n                self.logger.critical(\n                    'Database version mismatch: current version is %s but should be %s. Please run the \"%s\" script',\n                    result,\n                    version,\n                    script_name,\n                )\n            else:\n                self.logger.debug(\"Database version: %s is upto date\", version)\n        else:\n            self.logger.critical(\"Database version could not be verified.\")\n\n    def orders_invalidate(\n        self, uts: int = uts_now(), report_format: str = \"csv\", report_name: str = None\n    ) -> List[str]:\n        \"\"\"orders cleanup based on expiry date\"\"\"\n        self.logger.debug(\"Housekeeping.orders_invalidate(%s)\", uts)\n\n        with Order(self.debug, None, self.logger) as order:\n            # get expired orders\n            (field_list, order_list) = order.invalidate(timestamp=uts)\n            # normalize lists\n            (field_list, order_list) = self._lists_normalize(\n                field_list, order_list, \"order\"\n            )\n            # convert dates into human readable format\n            order_list = self._convert_data(order_list)\n\n            if report_name:\n                if order_list:\n                    # dump report to file\n                    if report_format == \"csv\":\n                        self.logger.debug(\n                            \"Housekeeping.orders_invalidate(): Dump in csv-format\"\n                        )\n                        csv_list = self._to_list(field_list, order_list)\n                        self._csv_dump(f\"{report_name}.{report_format}\", csv_list)\n                    elif report_format == \"json\":\n                        self.logger.debug(\n                            \"Housekeeping.orders_invalidate(): Dump in json-format\"\n                        )\n                        self._json_dump(f\"{report_name}.{report_format}\", order_list)\n                    else:\n                        self.logger.debug(\n                            \"Housekeeping.orders_invalidate():  No dump just return report\"\n                        )\n                else:\n                    self.logger.debug(\n                        \"Housekeeping.orders_invalidate(): No orders to dump\"\n                    )\n\n        return order_list\n\n    def parse(self, content: str) -> Dict[str, str]:\n        \"\"\"new oder request\"\"\"\n        self.logger.debug(\"Housekeeping.parse()\")\n\n        # def certreport_get(self, report_format='csv', report_name=None):\n        # check message\n        (\n            code,\n            message,\n            detail,\n            _protected,\n            payload,\n            _account_name,\n            permissions_dic,\n        ) = self.message.cli_check(content)\n\n        response_dic = {}\n        if code == 200:\n            if \"type\" in payload and \"data\" in payload:\n                if payload[\"type\"] == \"report\":\n                    (code, message, detail, response_dic) = self._clireport_get(\n                        payload, permissions_dic\n                    )\n                else:\n                    code = 400\n                    message = \"urn:ietf:params:acme:error:malformed\"\n                    detail = \"unknown type value\"\n            else:\n                code = 400\n                message = \"urn:ietf:params:acme:error:malformed\"\n                detail = \"either type field or data field is missing in payload\"\n\n        # prepare/enrich response\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        response_dic = self.message.prepare_response(response_dic, status_dic, False)\n        self.logger.debug(\"Housekeeping.parse() returned something.\")\n\n        return response_dic\n"
  },
  {
    "path": "acme_srv/message.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=r0913\n\"\"\"message class\"\"\"\nfrom __future__ import print_function\nimport json\nfrom typing import Tuple, Dict, Optional\nfrom dataclasses import dataclass\nfrom acme_srv.helper import (\n    decode_message,\n    load_config,\n    eab_handler_load,\n    uts_to_date_utc,\n    uts_now,\n)\nfrom acme_srv.error import Error\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.nonce import Nonce\nfrom acme_srv.signature import Signature\n\n\n@dataclass\nclass MessageConfiguration:\n    \"\"\"Contains message related configuration options.\"\"\"\n\n    signature_check_disable: bool = False\n    nonce_check_disable: bool = False\n    acct_path: str = \"/acme/acct/\"\n    revocation_path: str = \"/acme/revokecert\"\n    eabkid_check_disable: bool = False\n    invalid_eabkid_deactivate: bool = False\n    eab_handler: Optional[object] = None\n\n\nclass AccountRepository:\n    \"\"\"Repository for account related database operations\"\"\"\n\n    def __init__(self, dbstore):\n        self.dbstore = dbstore\n\n    def account_lookup(self, key, value):\n        \"\"\"Lookup an account by a given key and value.\"\"\"\n        return self.dbstore.account_lookup(key, value)\n\n    def account_update(self, data_dic, active):\n        \"\"\"Update account information in the database.\"\"\"\n        return self.dbstore.account_update(data_dic, active)\n\n    def cli_permissions_get(self, account_name):\n        \"\"\"Get CLI permissions for a specific account.\"\"\"\n        return self.dbstore.cli_permissions_get(account_name)\n\n\nclass Message(object):\n    \"\"\"Message handler\"\"\"\n\n    def __init__(\n        self, debug: bool = False, srv_name: str = None, logger: object = None\n    ):\n        self.debug = debug\n        self.logger = logger\n        self.nonce = Nonce(self.debug, self.logger)\n        self.dbstore = DBstore(self.debug, self.logger)\n        self.repo = AccountRepository(self.dbstore)\n        self.server_name = srv_name\n        self.config = self._load_configuration()\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Close the connection at the end of the context\"\"\"\n\n    def _load_configuration(self) -> MessageConfiguration:\n        \"\"\"Load and parse config from file and return MessageConfiguration dataclass.\"\"\"\n        self.logger.debug(\"Message._load_configuration()\")\n        config_dic = load_config()\n        msg_config = MessageConfiguration()\n        if \"Nonce\" in config_dic:\n            msg_config.nonce_check_disable = config_dic.getboolean(\n                \"Nonce\", \"nonce_check_disable\", fallback=False\n            )\n            msg_config.signature_check_disable = config_dic.getboolean(\n                \"Nonce\", \"signature_check_disable\", fallback=False\n            )\n        if \"EABhandler\" in config_dic:\n            if config_dic.getboolean(\n                \"EABhandler\", \"eabkid_check_disable\", fallback=False\n            ):\n                msg_config.eabkid_check_disable = True\n            elif \"eab_handler_file\" in config_dic[\"EABhandler\"]:\n                eab_handler_module = eab_handler_load(self.logger, config_dic)\n                if eab_handler_module:\n                    msg_config.invalid_eabkid_deactivate = config_dic.getboolean(\n                        \"EABhandler\", \"invalid_eabkid_deactivate\", fallback=False\n                    )\n                    msg_config.eab_handler = eab_handler_module.EABhandler\n                else:\n                    self.logger.critical(\"EABHandler could not get loaded\")\n            else:\n                self.logger.critical(\"EABHandler configuration incomplete\")\n        else:\n            msg_config.eabkid_check_disable = True\n\n        if \"Directory\" in config_dic and \"url_prefix\" in config_dic[\"Directory\"]:\n            url_prefix = config_dic[\"Directory\"][\"url_prefix\"]\n            msg_config.acct_path = url_prefix + \"/acme/acct/\"\n            msg_config.revocation_path = url_prefix + \"/acme/revokecert\"\n\n        self.logger.debug(\"Message._load_configuration() ended\")\n        return msg_config\n\n    _CHECK_EAB_CREDENTIALS_LOG_MSG = \"Message._check_and_handle_invalid_eab_credentials() ended with account_name: %s\"\n\n    def _check_and_handle_invalid_eab_credentials(self, account_name: str):\n        \"\"\"Check for accounts with invalid eab credentials.\"\"\"\n        self.logger.debug(\"Message._check_and_handle_invalid_eab_credentials()\")\n\n        account_dic = self._safe_account_lookup(account_name)\n        if not account_dic:\n            self.logger.error(\"Account lookup for %s failed.\", account_name)\n            self.logger.debug(\n                self._CHECK_EAB_CREDENTIALS_LOG_MSG,\n                None,\n            )\n            return None\n\n        eab_kid = account_dic.get(\"eab_kid\", None)\n        if not eab_kid:\n            self.logger.error(\"Account %s has no eab credentials\", account_name)\n            self.logger.debug(\n                self._CHECK_EAB_CREDENTIALS_LOG_MSG,\n                None,\n            )\n            return None\n\n        if self.config.eab_handler and not self._eab_mac_key_exists(eab_kid):\n            self._handle_missing_eab_credentials(account_name, eab_kid)\n            self.logger.debug(\n                self._CHECK_EAB_CREDENTIALS_LOG_MSG,\n                None,\n            )\n            return None\n        self.logger.debug(\n            self._CHECK_EAB_CREDENTIALS_LOG_MSG,\n            account_name,\n        )\n        return account_name\n\n    def _safe_account_lookup(self, account_name: str):\n        try:\n            return self.repo.account_lookup(\"name\", account_name)\n        except Exception as err:\n            self.logger.error(f\"Account lookup for {account_name} failed: {err}\")\n            return None\n\n    def _eab_mac_key_exists(self, eab_kid: str) -> bool:\n        try:\n            with self.config.eab_handler(self.logger) as eab_handler:\n                eab_mac_key = eab_handler.mac_key_get(eab_kid)\n                if not eab_mac_key:\n                    return False\n                return True\n        except Exception as err:\n            self.logger.error(f\"EAB handler error: {err}\")\n            return False\n\n    def _handle_missing_eab_credentials(self, account_name: str, eab_kid: str):\n        self.logger.error(\n            \"EAB credentials: %s could not be found in eab-credential store.\",\n            eab_kid,\n        )\n        if self.config.invalid_eabkid_deactivate:\n            self.logger.error(\n                \"Account %s will be deactivated due to missing eab credentials\",\n                account_name,\n            )\n            data_dic = {\n                \"name\": account_name,\n                \"status_id\": 7,\n                \"jwk\": f\"DEACTIVATED invalid_eabkid_deactivate {uts_to_date_utc(uts_now())}\",\n            }\n            try:\n                self.repo.account_update(data_dic, active=False)\n            except Exception as err:\n                self.logger.error(f\"Account update failed: {err}\")\n\n    def _extract_account_name_for_revocation(\n        self, content: Dict[str, str]\n    ) -> Optional[str]:\n        \"\"\"this is needed for cases where we get a revocation message signed with account key but account name is missing\"\"\"\n        self.logger.debug(\"Message._extract_account_name_for_revocation()\")\n\n        try:\n            account_list = self.repo.account_lookup(\"jwk\", json.dumps(content[\"jwk\"]))\n        except Exception as err_:\n            self.logger.critical(\n                f\"Database error: failed to look up account name for revocation: {err_}\"\n            )\n            return None\n        if account_list and \"name\" in account_list:\n            kid = account_list[\"name\"]\n        else:\n            kid = None\n\n        self.logger.debug(\n            \"Message._get_account_name_for_revocation() ended with kid: %s\", kid\n        )\n        return kid\n\n    def _extract_account_name_from_content(\n        self, content: Dict[str, str]\n    ) -> Optional[str]:\n        \"\"\"get name for account\"\"\"\n        self.logger.debug(\"Message._name_get(): content: %s\", content)\n\n        if \"kid\" in content:\n            self.logger.debug(\"Message._name_get(): kid: %s\", content[\"kid\"])\n            kid = content[\"kid\"].replace(\n                f\"{self.server_name}{self.config.acct_path}\", \"\"\n            )\n            if \"/\" in kid:\n                self.logger.debug(\"Message._name_get(): clear kid\")\n                kid = None\n        elif \"jwk\" in content and \"url\" in content:\n            self.logger.debug(\n                \"Message._name_get(): server_name: %s url: %s\",\n                self.server_name,\n                content[\"url\"],\n            )\n            if content[\"url\"] == f\"{self.server_name}{self.config.revocation_path}\":\n                self.logger.debug(\"Message._name_get(): revocation\")\n                kid = self._extract_account_name_for_revocation(content)\n            else:\n                kid = None\n        else:\n            kid = None\n\n        self.logger.debug(\n            \"Message._extract_account_name_from_content() returns: %s\", kid\n        )\n        return kid\n\n    def extract_account_name_from_content(\n        self, content: Dict[str, str]\n    ) -> Optional[str]:\n        \"\"\"public method to get name for account\"\"\"\n        self.logger.debug(\"Message.extract_account_name_from_content()\")\n        kid = self._extract_account_name_from_content(content)\n        self.logger.debug(\n            \"Message.extract_account_name_from_content() ended with: %s\", kid\n        )\n        return kid\n\n    def _check_nonce_for_replay_protection(\n        self, skip_nonce_check: bool, protected: Dict[str, str]\n    ) -> Tuple[int, Optional[str], Optional[str]]:\n        \"\"\"check nonce for anti replay protection\"\"\"\n        self.logger.debug(\"Message._check_nonce_for_replay_protection()\")\n        if skip_nonce_check or self.config.nonce_check_disable:\n            if self.config.nonce_check_disable:\n                self.logger.error(\n                    \"**** NONCE CHECK DISABLED!!! Severe security issue ****\"\n                )\n            else:\n                self.logger.info(\"Skip nonce check of inner payload during keyrollover\")\n            code = 200\n            message = None\n            detail = None\n        else:\n            (code, message, detail) = self.nonce.check(protected)\n\n        self.logger.debug(\n            \"Message._check_nonce_for_replay_protection() ended with: %s\", code\n        )\n        return (code, message, detail)\n\n    def _validate_message_and_check_signature(\n        self,\n        skip_nonce_check: bool,\n        skip_signature_check: bool,\n        content: str,\n        protected: Dict[str, str],\n        use_emb_key: bool,\n    ) -> Tuple[int, str, str, str]:\n        \"\"\"Decoding successful - check nonce for anti replay protection and signature.\"\"\"\n        self.logger.debug(\"Message._validate_message_and_check_signature()\")\n\n        (code, message, detail) = self._check_nonce_for_replay_protection(\n            skip_nonce_check, protected\n        )\n        account_name = None\n\n        # nonce check successful - get account name\n        account_name = self._extract_account_name_from_content(protected)\n        # check for invalid eab-credentials if not disabled and not using embedded key\n        if code == 200 and not self.config.eabkid_check_disable and not use_emb_key:\n            account_name = self._check_and_handle_invalid_eab_credentials(account_name)\n            if not account_name:\n                return (\n                    403,\n                    \"urn:ietf:params:acme:error:unauthorized\",\n                    \"invalid eab credentials\",\n                    None,\n                )\n\n        if code == 200 and not skip_signature_check:\n            signature = Signature(self.debug, self.server_name, self.logger)\n            (sig_check, error, error_detail) = signature.check(\n                account_name, content, use_emb_key, protected\n            )\n            if sig_check:\n                code = 200\n                message = None\n                detail = None\n            else:\n                code = 403\n                message = error\n                detail = error_detail\n\n            self.logger.debug(\n                \"Message._validate_message_and_check_signature() ended with: %s\", code\n            )\n        return (code, message, detail, account_name)\n\n    # pylint: disable=R0914\n    def check(\n        self, content: str, use_emb_key: bool = False, skip_nonce_check: bool = False\n    ) -> Tuple[int, str, str, Dict[str, str], Dict[str, str], str]:\n        \"\"\"validate message\"\"\"\n        self.logger.debug(\"Message.check()\")\n\n        # disable signature check if paramter has been set\n        if self.config.signature_check_disable:\n            self.logger.error(\n                \"**** SIGNATURE_CHECK_DISABLE!!! Severe security issue ****\"\n            )\n            skip_signature_check = True\n        else:\n            skip_signature_check = False\n\n        # decode message\n        (result, error_detail, protected, payload, _signature) = decode_message(\n            self.logger, content\n        )\n        account_name = None\n        if result:\n            (\n                code,\n                message,\n                detail,\n                account_name,\n            ) = self._validate_message_and_check_signature(\n                skip_nonce_check, skip_signature_check, content, protected, use_emb_key\n            )\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:malformed\"\n            detail = error_detail\n\n        self.logger.debug(\"Message._check() ended with:%s\", code)\n        return (code, message, detail, protected, payload, account_name)\n\n    def cli_check(\n        self, content: str\n    ) -> Tuple[int, str, str, Dict[str, str], Dict[str, str], str, Dict[str, str]]:\n        \"\"\"validate message coming from CLI client\"\"\"\n        self.logger.debug(\"Message.cli_check()\")\n\n        # decode message\n        (result, error_detail, protected, payload, _signature) = decode_message(\n            self.logger, content\n        )\n        account_name = None\n        permissions = {}\n        if result:\n            # check signature\n            account_name = self._extract_account_name_from_content(protected)\n            signature = Signature(self.debug, self.server_name, self.logger)\n            (sig_check, error, error_detail) = signature.cli_check(\n                account_name, content\n            )\n            if sig_check:\n                code = 200\n                message = None\n                detail = None\n                try:\n                    permissions = self.repo.cli_permissions_get(account_name)\n                except Exception as err:\n                    self.logger.error(f\"cli_permissions_get failed: {err}\")\n                    permissions = {}\n            else:\n                code = 403\n                message = error\n                detail = error_detail\n        else:\n            # message could not get decoded\n            code = 400\n            message = \"urn:ietf:params:acme:error:malformed\"\n            detail = error_detail\n\n        self.logger.debug(\"Message.cli_check() ended with:%s\", code)\n        return (code, message, detail, protected, payload, account_name, permissions)\n\n    def prepare_response(\n        self,\n        response_dic: Dict[str, str],\n        status_dic: Dict[str, str],\n        add_nonce: bool = True,\n    ) -> Dict[str, str]:\n        \"\"\"prepare response_dic\"\"\"\n        self.logger.debug(\"Message.prepare_response()\")\n        if \"code\" not in status_dic:\n            status_dic[\"code\"] = 500\n            status_dic[\"type\"] = \"urn:ietf:params:acme:error:serverInternal\"\n            status_dic[\"detail\"] = \"http status code missing\"\n\n        if \"type\" not in status_dic:\n            status_dic[\"type\"] = \"urn:ietf:params:acme:error:serverInternal\"\n\n        if \"detail\" not in status_dic:\n            status_dic[\"detail\"] = None\n\n        # create response\n        response_dic[\"code\"] = status_dic[\"code\"]\n\n        # create header if not existing\n        if \"header\" not in response_dic:\n            response_dic[\"header\"] = {}\n\n        if status_dic[\"code\"] >= 400:\n            if status_dic[\"detail\"]:\n                # some error occured get details\n                error_message = Error(self.debug, self.logger)\n                status_dic[\"detail\"] = error_message.enrich_error(\n                    status_dic[\"type\"], status_dic[\"detail\"]\n                )\n                response_dic[\"data\"] = {\n                    \"status\": status_dic[\"code\"],\n                    \"type\": status_dic[\"type\"],\n                    \"detail\": status_dic[\"detail\"],\n                }\n            else:\n                response_dic[\"data\"] = {\n                    \"status\": status_dic[\"code\"],\n                    \"type\": status_dic[\"type\"],\n                }\n\n        # always add nonce to header\n        if add_nonce:\n            response_dic[\"header\"][\"Replay-Nonce\"] = self.nonce.generate_and_add()\n\n        return response_dic\n"
  },
  {
    "path": "acme_srv/monkey_patches.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Monkey patches class\"\"\"\n# pylint: disable=c0413, c0415, e0401, e1121\nfrom django.db import DEFAULT_DB_ALIAS\nfrom django.db import transaction\n\n\ndef django_sqlite_atomic():  # NOSONAR\n    \"\"\"monkey patch for django deployments fixing database lock issues\"\"\"\n\n    def atomic(using: str = None, savepoint: bool = True, immediate: bool = False):\n        # Bare decorator: @atomic -- although the first argument is called\n        # `using`, it's actually the function being decorated.\n        if callable(using):\n            atomic_ = transaction.Atomic(DEFAULT_DB_ALIAS, savepoint, True)(using)\n        # Decorator: @atomic(...) or context manager: with atomic(...): ...\n        else:\n            atomic_ = transaction.Atomic(using, savepoint, True)\n\n        atomic_.immediate = immediate\n        return atomic_\n\n    def __enter__(self):\n        \"\"\"enter function\"\"\"\n        connection = transaction.get_connection(self.using)\n        if not connection.in_atomic_block:\n            # Reset state when entering an outermost atomic block.\n            connection.commit_on_exit = True\n            connection.needs_rollback = False\n            if not connection.get_autocommit():\n                # Pretend we're already in an atomic block to bypass the code\n                # that disables autocommit to enter a transaction, and make a\n                # note to deal with this case in __exit__.\n                connection.in_atomic_block = True\n                connection.commit_on_exit = False\n\n        if connection.in_atomic_block:\n            # We're already in a transaction; create a savepoint, unless we\n            # were told not to or we're already waiting for a rollback. The\n            # second condition avoids creating useless savepoints and prevents\n            # overwriting needs_rollback until the rollback is performed.\n            if self.savepoint and not connection.needs_rollback:\n                sid = connection.savepoint()\n                connection.savepoint_ids.append(sid)\n            else:\n                connection.savepoint_ids.append(None)\n        else:\n            if self.immediate:\n                connection.set_autocommit(False)\n                connection.cursor().execute(\"BEGIN IMMEDIATE\")\n\n            else:\n                connection.set_autocommit(\n                    False, force_begin_transaction_with_broken_autocommit=True\n                )\n\n            connection.in_atomic_block = True\n\n    transaction.atomic = atomic\n    transaction.Atomic.immediate = False\n    transaction.Atomic.__enter__ = __enter__\n\n\ndjango_sqlite_atomic()\n"
  },
  {
    "path": "acme_srv/nonce.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Nonce class\"\"\"\nfrom __future__ import print_function\nimport uuid\nfrom typing import Tuple, Dict\n\nfrom acme_srv.db_handler import DBstore\n\n\nclass NonceRepository:\n    \"\"\"Repository class for Nonce operations.\"\"\"\n\n    def __init__(self, dbstore) -> None:\n        self.dbstore = dbstore\n\n    def check_nonce(self, nonce) -> bool:\n        return self.dbstore.nonce_check(nonce)\n\n    def delete_nonce(self, nonce) -> None:\n        return self.dbstore.nonce_delete(nonce)\n\n    def add_nonce(self, nonce) -> int:\n        return self.dbstore.nonce_add(nonce)\n\n\nclass Nonce(object):\n    \"\"\"Nonce handler\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None, repo: object = None):\n        self.debug = debug\n        self.logger = logger\n        self.repo = repo or NonceRepository(DBstore(self.debug, self.logger))\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Close the connection at the end of the context\"\"\"\n\n    def _validate_and_consume_nonce(self, nonce: str) -> Tuple[int, str, str]:\n        \"\"\"Check if nonce exists and delete it (consume).\"\"\"\n        self.logger.debug(\"Nonce._validate_and_consume_nonce(%s)\", nonce)\n        try:\n            nonce_chk_result = self.repo.check_nonce(nonce)\n        except Exception as err_:\n            self.logger.critical(\"Database error: failed to check nonce: %s\", err_)\n            nonce_chk_result = False\n\n        if nonce_chk_result:\n            try:\n                self.repo.delete_nonce(nonce)\n            except Exception as err_:\n                self.logger.critical(\"Database error: failed to delete nonce: %s\", err_)\n            code = 200\n            message = None\n            detail = None\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:badNonce\"\n            detail = nonce\n        self.logger.debug(\"Nonce._validate_and_consume_nonce() ended with:%s\", code)\n        return (code, message, detail)\n\n    def _generate_nonce_value(self) -> str:\n        \"\"\"Generate a new nonce value.\"\"\"\n        self.logger.debug(\"Nonce._generate_nonce_value()\")\n        return uuid.uuid4().hex\n\n    def check(self, protected_decoded: Dict[str, str]) -> Tuple[int, str, str]:\n        \"\"\"Check nonce (public method, backward compatible).\"\"\"\n        self.logger.debug(\"Nonce.check_nonce()\")\n        if \"nonce\" in protected_decoded:\n            (code, message, detail) = self._validate_and_consume_nonce(\n                protected_decoded[\"nonce\"]\n            )\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:badNonce\"\n            detail = \"NONE\"\n        self.logger.debug(\"Nonce.check_nonce() ended with:%s\", code)\n        return (code, message, detail)\n\n    def generate_and_add(self) -> str:\n        \"\"\"Generate new nonce and store it (public method, backward compatible).\"\"\"\n        self.logger.debug(\"Nonce.generate_and_add()\")\n        nonce = self._generate_nonce_value()\n        self.logger.debug(\"got nonce: %s\", nonce)\n        try:\n            self.repo.add_nonce(nonce)\n        except Exception as err_:\n            self.logger.critical(\"Database error: failed to add new nonce: %s\", err_)\n        self.logger.debug(\"Nonce.generate_and_add() ended with:%s\", nonce)\n        return nonce\n"
  },
  {
    "path": "acme_srv/order.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Order class\"\"\"\nfrom __future__ import print_function\nimport json\nimport copy\nfrom typing import Any, List, Tuple, Dict, Optional\nfrom dataclasses import dataclass, field\nfrom acme_srv.helper import (\n    b64_url_recode,\n    config_allowed_domainlist_load,\n    config_profile_load,\n    error_dic_get,\n    generate_random_string,\n    load_config,\n    parse_url,\n    uts_to_date_utc,\n    uts_now,\n    validate_identifier,\n    is_domain_whitelisted,\n    config_eab_profile_load,\n)\nfrom acme_srv.certificate import Certificate\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.message import Message\n\n\nclass OrderDatabaseError(Exception):\n    \"\"\"Exception raised for database-related errors in Order operations.\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\nclass OrderValidationError(Exception):\n    \"\"\"Exception raised for validation errors in Order operations.\"\"\"\n\n    # pylint: disable=unnecessary-pass\n    pass\n\n\nclass OrderRepository:\n    \"\"\"Repository for all Order-related database operations.\"\"\"\n\n    def __init__(self, dbstore, logger):\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def add_order(self, data_dic):\n        \"\"\"Add a new order to the database.\"\"\"\n        try:\n            return self.dbstore.order_add(data_dic)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to add order: %s\", err)\n            raise OrderDatabaseError(f\"Failed to add order: {err}\") from err\n\n    def add_authorization(self, auth):\n        \"\"\"Add a new authorization to the database.\"\"\"\n        try:\n            return self.dbstore.authorization_add(auth)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to add authorization: %s\", err)\n            raise OrderDatabaseError(f\"Failed to add authorization: {err}\") from err\n\n    def update_authorization(self, auth):\n        \"\"\"Update an existing authorization in the database.\"\"\"\n        try:\n            return self.dbstore.authorization_update(auth)\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to update authorization: %s\", err\n            )\n            raise OrderDatabaseError(f\"Failed to update authorization: {err}\") from err\n\n    def order_lookup(self, key, value):\n        \"\"\"Look up an order in the database.\"\"\"\n        try:\n            return self.dbstore.order_lookup(key, value)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to look up order: %s\", err)\n            raise OrderDatabaseError(f\"Failed to look up order: {err}\") from err\n\n    def order_update(self, data_dic):\n        \"\"\"Update an existing order in the database.\"\"\"\n        try:\n            return self.dbstore.order_update(data_dic)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to update order: %s\", err)\n            raise OrderDatabaseError(f\"Failed to update order: {err}\") from err\n\n    def authorization_lookup(self, key, value, fields):\n        \"\"\"Look up an authorization in the database.\"\"\"\n        try:\n            return self.dbstore.authorization_lookup(key, value, fields)\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to look up authorization: %s\", err\n            )\n            raise OrderDatabaseError(f\"Failed to look up authorization: {err}\") from err\n\n    def account_lookup(self, key, value):\n        \"\"\"Look up an account in the database.\"\"\"\n        try:\n            return self.dbstore.account_lookup(key, value)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to look up account: %s\", err)\n            raise OrderDatabaseError(f\"Failed to look up account: {err}\") from err\n\n    def certificate_lookup(self, key, value):\n        \"\"\"Look up a certificate in the database.\"\"\"\n        try:\n            return self.dbstore.certificate_lookup(key, value)\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to look up certificate: %s\", err\n            )\n            raise OrderDatabaseError(f\"Failed to look up certificate: {err}\") from err\n\n    def hkparameter_get(self, param):\n        \"\"\"Get a hkparameter from the database.\"\"\"\n        try:\n            return self.dbstore.hkparameter_get(param)\n        except Exception as err:\n            self.logger.critical(\"Database error: failed to get hkparameter: %s\", err)\n            raise OrderDatabaseError(f\"Failed to get hkparameter: {err}\") from err\n\n    def orders_invalid_search(self, order_field, timestamp, vlist, operant):\n        \"\"\"Search for invalid orders in the database.\"\"\"\n        try:\n            return self.dbstore.orders_invalid_search(\n                order_field, timestamp, vlist=vlist, operant=operant\n            )\n        except Exception as err:\n            self.logger.critical(\n                \"Database error: failed to search for invalid orders: %s\", err\n            )\n            raise OrderDatabaseError(\n                f\"Failed to search for invalid orders: {err}\"\n            ) from err\n\n\n@dataclass\nclass OrderConfiguration:\n    \"\"\"Configuration parameters for Order handling\"\"\"\n\n    validity: int = 86400\n    authz_validity: int = 86400\n    expiry_check_disable: bool = False\n    retry_after: int = 600\n    tnauthlist_support: bool = False\n    email_identifier_support: bool = False\n    email_identifier_rewrite: bool = False\n    sectigo_sim: bool = False\n    identifier_limit: int = 20\n    header_info_list: List[Any] = field(default_factory=list)\n    profiles: Dict[str, Any] = field(default_factory=dict)\n    profiles_sync: bool = False\n    profiles_check_disable: bool = True\n    idempotent_finalize: bool = False\n    allowed_domainlist: List[str] = field(default_factory=list)\n    eab_profiling: bool = False\n    eab_handler: Optional[Any] = None\n\n\nclass Order(object):\n    \"\"\"class for order handling\"\"\"\n\n    def __init__(\n        self, debug: bool = None, server_name: str = None, logger: object = None\n    ) -> None:\n        \"\"\"Initialize the Order handler\"\"\"\n        self.debug = debug\n        self.server_name = server_name\n        self.config = OrderConfiguration()\n        self.logger = logger\n        self.dbstore = DBstore(self.debug, self.logger)\n        self.path_dic = {\n            \"authz_path\": \"/acme/authz/\",\n            \"order_path\": \"/acme/order/\",\n            \"cert_path\": \"/acme/cert/\",\n        }\n        self.repository = OrderRepository(self.dbstore, self.logger)\n        self.message = Message(self.debug, self.server_name, self.logger)\n        self.error_msg_dic = error_dic_get(self.logger)\n\n    def __enter__(self) -> \"Order\":\n        \"\"\"Enter the context manager, loading configuration.\"\"\"\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args) -> None:\n        \"\"\"\n        Exit the context manager. (No-op, placeholder for cleanup.)\n        \"\"\"\n\n    def _add_authorizations_to_db(\n        self, oid: str, payload: Dict[str, str], auth_dic: Dict[str, str]\n    ) -> str:\n        \"\"\"Add authorizations to the database for the given order id. Returns error message or None.\"\"\"\n        self.logger.debug(\"Order._add_authorizations_to_db(%s)\", oid)\n\n        if oid:\n            error = None\n            for auth in payload[\"identifiers\"]:\n                auth_name = generate_random_string(self.logger, 12)\n                auth_dic[auth_name] = auth.copy()\n                auth[\"name\"] = auth_name\n                auth[\"order\"] = oid\n                auth[\"status\"] = \"pending\"\n                auth[\"expires\"] = uts_now() + self.config.authz_validity\n                try:\n                    self.repository.add_authorization(auth)\n                    if self.config.sectigo_sim:\n                        auth[\"status\"] = \"valid\"\n                        self.repository.update_authorization(auth)\n                except Exception as err_:\n                    self.logger.critical(\n                        \"Database error: failed to add authorization: %s\", err_\n                    )\n        else:\n            error = self.error_msg_dic[\"malformed\"]\n\n        self.logger.debug(\"Order._add_authorizations_to_db() ended with %s\", error)\n        return error\n\n    def is_profile_valid(self, profile: str) -> str:\n        \"\"\"Check if the given profile is valid.\"\"\"\n        self.logger.debug(\"Order.is_profile_valid(%s)\", profile)\n        error = self.error_msg_dic[\"invalidprofile\"]\n        if self.config.profiles_check_disable:\n            self.logger.debug(\"Order.is_profile_valid(): profile check disabled\")\n            error = None\n        else:\n            if profile in self.config.profiles:\n                error = None\n            else:\n                self.logger.warning(\n                    \"Profile '%s' is not valid. Ignoring submitted profile.\", profile\n                )\n        self.logger.debug(\"Order.is_profile_valid() ended with %s\", error)\n        return error\n\n    def _add_order_and_authorizations(\n        self,\n        data_dic: Dict[str, str],\n        auth_dic: Dict[str, str],\n        payload: Dict[str, str],\n        error: Optional[str] = None,\n    ) -> Tuple[str, Dict[str, str]]:\n        \"\"\"Add order and its authorizations to the database. Returns error message or None.\"\"\"\n        self.logger.debug(\"Order._add_order_and_authorizations()\")\n\n        try:\n            oid = self.repository.add_order(data_dic)\n        except Exception as err_:\n            self.logger.critical(\"Database error: failed to add order: %s\", err_)\n            oid = None\n\n        if not error:\n            error = self._add_authorizations_to_db(oid, payload, auth_dic)\n\n        self.logger.debug(\"Order._add_order_and_authorizations() ended with %s\", error)\n        return error\n\n    def add_profile_to_order(\n        self, data_dic: Dict[str, str], payload: Dict[str, str]\n    ) -> Tuple[str, Dict[str, str]]:\n        \"\"\"Add a profile to the order if valid.\"\"\"\n        self.logger.debug(\"Order.add_profile_to_order(%s)\", data_dic)\n        error = self.is_profile_valid(payload[\"profile\"])\n        if not error:\n            if self.config.profiles:\n                data_dic[\"profile\"] = payload[\"profile\"]\n            else:\n                self.logger.warning(\n                    \"Ignore submitted profile '%s' as no profiles are configured.\",\n                    payload[\"profile\"],\n                )\n        self.logger.debug(\"Order.add_profile_to_order() ended with %s\", error)\n        return error, data_dic\n\n    def _apply_eab_profile(self, account_name: str) -> None:\n        \"\"\"Apply EAB profile settings to the order configuration.\"\"\"\n        self.logger.debug(\n            \"Order._apply_eab_profile() - apply eab profile setting for account %s\",\n            account_name,\n        )\n\n        if not self.config.eab_profiling:\n            return\n\n        try:\n            account_dic = self.repository.account_lookup(\"name\", account_name)\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to look up account list: %s\", err_\n            )\n            account_dic = {}\n\n        eab_kid = account_dic.get(\"eab_kid\") if account_dic else None\n\n        if not eab_kid:\n            return\n\n        try:\n            with self.config.eab_handler(self.logger) as eab_handler:\n                profile_dic = eab_handler.key_file_load()\n                allowed_domainlist = (\n                    profile_dic.get(eab_kid, {})\n                    .get(\"order\", {})\n                    .get(\"allowed_domainlist\")\n                )\n                if not allowed_domainlist:\n                    allowed_domainlist = (\n                        profile_dic.get(eab_kid, {})\n                        .get(\"cahandler\", {})\n                        .get(\"allowed_domainlist\")\n                    )\n                    if allowed_domainlist:\n                        self.logger.warning(\n                            \"allowed_domainlist parameter found in cahandler section of the eab-profile - this is deprecated, please use the order section\"\n                        )\n                if allowed_domainlist:\n                    self.logger.debug(\n                        \"Order._apply_eab_profile() - apply allowed_domainlist from eab profile.\"\n                    )\n                    self.config.allowed_domainlist = allowed_domainlist\n        except Exception as err:\n            self.logger.error(\n                \"Failed to process EAB profile for Account %s (kid: %s): %s\",\n                account_name,\n                eab_kid,\n                err,\n            )\n\n    def create_order(\n        self, payload: Dict[str, str], account_name: str\n    ) -> Tuple[str, str, Dict[str, str], int]:\n        \"\"\"Create a new order and add it to the database.\"\"\"\n        self.logger.debug(\"Order.create_order(%s)\", account_name)\n\n        error = None\n        detail = None\n        auth_dic = {}\n        order_name = generate_random_string(self.logger, 12)\n        expires = uts_now() + self.config.validity\n\n        # apply eab profiling if enabled\n        if self.config.eab_profiling and self.config.eab_handler:\n            self._apply_eab_profile(account_name)\n\n        if \"identifiers\" in payload:\n            data_dic = {\"status\": 2, \"expires\": expires, \"account\": account_name}\n            data_dic[\"name\"] = order_name\n            data_dic[\"identifiers\"] = json.dumps(payload[\"identifiers\"])\n            error, detail = self._check_identifiers_validity(payload[\"identifiers\"])\n            if error:\n                data_dic[\"status\"] = 1\n            else:\n                if \"profile\" in payload:\n                    (error, data_dic) = self.add_profile_to_order(data_dic, payload)\n                    if error == self.error_msg_dic[\"invalidprofile\"]:\n                        detail = \"Invalid profile specified\"\n            error = self._add_order_and_authorizations(\n                data_dic, auth_dic, payload, error\n            )\n        else:\n            error = self.error_msg_dic[\"unsupportedidentifier\"]\n\n        self.logger.debug(\"Order.create_order() ended\")\n        return (error, detail, order_name, auth_dic, uts_to_date_utc(expires))\n\n    def _load_header_info_config(self, config_dic: Dict[str, str]):\n        \"\"\"Load header info list from config file.\"\"\"\n        self.logger.debug(\"Order._load_header_info_config()\")\n        if \"Order\" in config_dic and \"header_info_list\" in config_dic[\"Order\"]:\n            try:\n                self.config.header_info_list = json.loads(\n                    config_dic[\"Order\"][\"header_info_list\"]\n                )\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse header_info_list from configuration: %s\",\n                    err_,\n                )\n        self.logger.debug(\"Order._load_header_info_config() ended\")\n\n    def _load_order_config(self, config_dic: Dict[str, str]):\n        \"\"\"Load order-related configuration from file.\"\"\"\n        self.logger.debug(\"Order._load_order_config()\")\n        if \"Challenge\" in config_dic:\n            self.config.sectigo_sim = config_dic.getboolean(\n                \"Challenge\", \"sectigo_sim\", fallback=False\n            )\n        if \"Order\" in config_dic:\n            self.config.tnauthlist_support = config_dic.getboolean(\n                \"Order\", \"tnauthlist_support\", fallback=False\n            )\n            self.config.email_identifier_support = config_dic.getboolean(\n                \"Order\", \"email_identifier_support\", fallback=False\n            )\n            self.config.email_identifier_rewrite = config_dic.getboolean(\n                \"Order\", \"email_identifier_rewrite\", fallback=False\n            )\n            self.config.expiry_check_disable = config_dic.getboolean(\n                \"Order\", \"expiry_check_disable\", fallback=False\n            )\n            self.config.idempotent_finalize = config_dic.getboolean(\n                \"Order\", \"idempotent_finalize\", fallback=False\n            )\n            try:\n                self.config.retry_after = int(\n                    config_dic.get(\n                        \"Order\", \"retry_after_timeout\", fallback=self.config.retry_after\n                    )\n                )\n            except Exception:\n                self.logger.warning(\n                    \"Failed to parse retry_after from configuration: %s\",\n                    config_dic[\"Order\"].get(\"retry_after_timeout\", None),\n                )\n            try:\n                self.config.validity = int(\n                    config_dic.get(\"Order\", \"validity\", fallback=self.config.validity)\n                )\n            except Exception:\n                self.logger.warning(\n                    \"Failed to parse validity from configuration: %s\",\n                    config_dic[\"Order\"].get(\"validity\", None),\n                )\n            try:\n                self.config.identifier_limit = int(\n                    config_dic.get(\"Order\", \"identifier_limit\", fallback=20)\n                )\n            except Exception:\n                self.logger.warning(\n                    \"Failed to parse identifier_limit from configuration: %s\",\n                    config_dic[\"Order\"].get(\"identifier_limit\", None),\n                )\n        self.logger.debug(\"Order._load_order_config() ended\")\n\n    def _load_profile_config(self, config_dic: Dict[str, str]):\n        \"\"\"Load profiles from file or database.\"\"\"\n        self.logger.debug(\"Order._load_profile_config()\")\n        self._load_profiles_from_config(config_dic)\n        self._load_profiles_from_db_if_sync(config_dic)\n        self._maybe_disable_profile_check(config_dic)\n        self.logger.debug(\"Order._load_profile_config() ended\")\n\n    def _load_profiles_from_config(self, config_dic: Dict[str, str]):\n        \"\"\"Load profiles from configuration file.\"\"\"\n        if \"Order\" in config_dic and \"profiles\" in config_dic[\"Order\"]:\n            self.logger.debug(\"Order._config_load(): profile check enabled\")\n            self.config.profiles_check_disable = False\n            self.config.profiles = config_profile_load(self.logger, config_dic)\n\n    def _load_profiles_from_db_if_sync(self, config_dic: Dict[str, str]):\n        \"\"\"Load profiles from database if profiles_sync is set.\"\"\"\n        if \"CAhandler\" in config_dic and \"profiles_sync\" in config_dic[\"CAhandler\"]:\n            self.config.profiles_sync = config_dic.getboolean(\n                \"CAhandler\", \"profiles_sync\", fallback=False\n            )\n            if self.config.profiles_sync:\n                self.logger.debug(\n                    \"Order._config_load(): profile_sync set. Loading profiles\"\n                )\n                try:\n                    profiles = self.repository.hkparameter_get(\"profiles\")\n                except Exception as err:\n                    self.logger.critical(\n                        \"Database error: failed to get profile list: %s\", err\n                    )\n                    profiles = None\n                if profiles:\n                    self._set_profiles_from_db(profiles)\n\n    def _set_profiles_from_db(self, profiles):\n        \"\"\"Set profiles from database string.\"\"\"\n        try:\n            profile_dic = json.loads(profiles)\n            self.config.profiles = profile_dic.get(\"profiles\", {})\n        except Exception as err_:\n            self.logger.error(\n                \"Error when loading the profiles parameter from database: %s\", err_\n            )\n\n    def _maybe_disable_profile_check(self, config_dic: Dict[str, str]):\n        \"\"\"Disable profile check\"\"\"\n        if self.config.profiles and \"Order\" in config_dic:\n            self.config.profiles_check_disable = config_dic.getboolean(\n                \"Order\", \"profiles_check_disable\", fallback=False\n            )\n\n    def _load_configuration(self):\n        \"\"\"Load all configuration from file.\"\"\"\n        self.logger.debug(\"Order._load_configuration()\")\n        config_dic = load_config()\n        # load order config\n        self._load_order_config(config_dic)\n        self._load_header_info_config(config_dic)\n        if \"Authorization\" in config_dic:\n            try:\n                self.config.authz_validity = int(\n                    config_dic.get(\n                        \"Authorization\", \"validity\", fallback=self.config.authz_validity\n                    )\n                )\n            except Exception:\n                self.logger.warning(\n                    \"Failed to parse authz validity from configuration: %s\",\n                    config_dic[\"Authorization\"].get(\"validity\", None),\n                )\n        if \"Directory\" in config_dic and \"url_prefix\" in config_dic[\"Directory\"]:\n            self.path_dic = {\n                k: config_dic[\"Directory\"][\"url_prefix\"] + v\n                for k, v in self.path_dic.items()\n            }\n        self._load_profile_config(config_dic)\n\n        # load allowed domainlist\n        self.config.allowed_domainlist = config_allowed_domainlist_load(\n            self.logger, config_dic\n        )\n        # load profiling\n        (\n            self.config.eab_profiling,\n            self.config.eab_handler,\n        ) = config_eab_profile_load(self.logger, config_dic)\n\n        self.logger.debug(\"Order._config_load() ended.\")\n\n    def _name_get(self, url: str) -> str:\n        \"\"\"get ordername\"\"\"\n        self.logger.debug(\"Order._name_get(%s)\", url)\n\n        url_dic = parse_url(self.logger, url)\n        order_name = url_dic[\"path\"].replace(self.path_dic[\"order_path\"], \"\")\n        if \"/\" in order_name:\n            (order_name, _sinin) = order_name.split(\"/\", 1)\n        self.logger.debug(\"Order._name_get() ended\")\n        return order_name\n\n    def are_identifiers_allowed(self, identifiers_list: List[str]) -> Tuple[str, str]:\n        \"\"\"Check if the provided identifiers are allowed.\"\"\"\n        self.logger.debug(\"Order.are_identifiers_allowed()\")\n        error = None\n        detail = None\n        allowed_identifiers = self._get_allowed_identifier_types()\n        for identifier in identifiers_list:\n            error, detail = self._check_single_identifier(\n                identifier, allowed_identifiers\n            )\n            if error:\n                break\n        self.logger.debug(\"Order.are_identifiers_allowed() ended with: %s\", error)\n        return error, detail\n\n    def _get_allowed_identifier_types(self) -> List[str]:\n        allowed = [\"dns\", \"ip\"]\n        if self.config.tnauthlist_support:\n            allowed.append(\"tnauthlist\")\n        if self.config.email_identifier_support:\n            allowed.append(\"email\")\n        return allowed\n\n    def _check_single_identifier(\n        self, identifier: dict, allowed_identifiers: List[str]\n    ) -> Tuple[str, str]:\n        \"\"\"Check if a single identifier is allowed.\"\"\"\n        self.logger.debug(\"Order._check_single_identifier(%s)\", identifier)\n\n        # check if type is present\n        if \"type\" not in identifier:\n            self.logger.error(\"Identifier type is missing\")\n            return self.error_msg_dic[\"malformed\"], \"Identifier type is missing\"\n\n        # check if value is present\n        if \"value\" not in identifier:\n            self.logger.error(\"Identifier value is missing\")\n            return self.error_msg_dic[\"malformed\"], \"Identifier value is missing\"\n\n        # check if type is allowd\n        id_type = identifier[\"type\"].lower()\n        if id_type not in allowed_identifiers:\n            self.logger.error(\"Identifier type %s not supported\", identifier[\"type\"])\n            return (\n                self.error_msg_dic[\"unsupportedidentifier\"],\n                f'Identifier type {identifier[\"type\"]} not supported',\n            )\n\n        # check if value is valid\n        if not validate_identifier(\n            self.logger,\n            id_type,\n            identifier[\"value\"],\n            self.config.tnauthlist_support,\n        ):\n            self.logger.error(\n                \"Identifier value %s not allowed for type %s\",\n                identifier[\"value\"],\n                identifier[\"type\"],\n            )\n            return (\n                self.error_msg_dic[\"rejectedidentifier\"],\n                f'identifier value {identifier[\"value\"]} not allowed',\n            )\n\n        # check allowed domainlist for dns identifiers\n        if (\n            id_type == \"dns\"\n            and self.config.allowed_domainlist\n            and not is_domain_whitelisted(\n                self.logger,\n                identifier[\"value\"],\n                self.config.allowed_domainlist,\n            )\n        ):\n            self.logger.error(\n                \"FQDN/SAN %s not allowed by configuration\",\n                identifier[\"value\"],\n            )\n            return (\n                self.error_msg_dic[\"rejectedidentifier\"],\n                f'FQDN/SAN {identifier[\"value\"]} not allowed by configuration',\n            )\n        return None, None\n\n    def _rewrite_email_identifiers(\n        self, identifiers_list: List[Dict[str, str]]\n    ) -> List[Dict[str, str]]:\n        \"\"\"Rewrite DNS identifiers with @ to email identifiers.\"\"\"\n        self.logger.debug(\"Order._rewrite_email_identifiers()\")\n\n        if (\n            self.config.email_identifier_support\n            and self.config.email_identifier_rewrite\n        ):\n            identifiers_modified = []\n            for ident in identifiers_list:\n                if (\n                    \"type\" in ident\n                    and \"value\" in ident\n                    and ident[\"type\"].lower() == \"dns\"\n                    and \"@\" in ident[\"value\"]\n                ):\n                    self.logger.info(\n                        \"Rewrite DNS identifier '%s' to email identifier\",\n                        ident[\"value\"],\n                    )\n                    ident[\"type\"] = \"email\"\n                identifiers_modified.append(ident)\n        else:\n            identifiers_modified = identifiers_list\n\n        self.logger.debug(\"Order._rewrite_email_identifiers() ended\")\n        return identifiers_modified\n\n    def _check_identifier_limit(self, identifiers_list: List[str]) -> bool:\n        \"\"\"Check and log if identifier limit is exceeded.\"\"\"\n        self.logger.debug(\"Order._check_identifier_limit()\")\n        error = False\n        if len(identifiers_list) > self.config.identifier_limit:\n            self.logger.warning(\n                \"Number of identifiers %d exceeds limit %d\",\n                len(identifiers_list),\n                self.config.identifier_limit,\n            )\n            error = True\n        return error\n\n    def _check_identifiers_validity(\n        self, identifiers_list: List[str]\n    ) -> Tuple[str, str]:\n        \"\"\"Check validity of identifiers in the order.\"\"\"\n        self.logger.debug(\"Order._check_identifiers_validity(%s)\", identifiers_list)\n        # make a deep copy to avoid modifying the original list\n        identifiers_list = copy.deepcopy(identifiers_list)\n\n        if identifiers_list and isinstance(identifiers_list, list):\n\n            # rewrite email identifiers if configured\n            identifiers_list = self._rewrite_email_identifiers(identifiers_list)\n\n            # check identifier limit\n            if self._check_identifier_limit(identifiers_list):\n                return (\n                    self.error_msg_dic[\"rejectedidentifier\"],\n                    \"identifier limit exceeded\",\n                )\n\n            # check if identifier types and values are allowed\n            error, detail = self.are_identifiers_allowed(identifiers_list)\n            if error:\n                self.logger.debug(\n                    \"Order._check_identifiers_validity() ended with %s:\", error\n                )\n                return error, detail\n\n        else:\n            # malformed identifiers list\n            error = self.error_msg_dic[\"malformed\"]\n            detail = \"malformed identifiers list\"\n\n        self.logger.debug(\"Order._check_identifiers_validity() done with %s:\", error)\n        return error, detail\n\n    def _get_order_info(self, order_name: str) -> Dict[str, str]:\n        \"\"\"List details of an order. Returns order dict or empty dict on error.\"\"\"\n        self.logger.debug(\"Order._get_order_info(%s)\", order_name)\n        try:\n            result = self.repository.order_lookup(\"name\", order_name)\n        except Exception as err_:\n            self.logger.critical(\"Database error: failed to look up order: %s\", err_)\n            result = None\n        return result\n\n    def _header_info_lookup(self, header: Optional[Dict[str, Any]]) -> str:\n        \"\"\"lookup header information and serialize them in a string\"\"\"\n        self.logger.debug(\"Order._header_info_lookup()\")\n\n        header_info_dic = {}\n        if header and self.config.header_info_list:\n            for ele in self.config.header_info_list:\n                if ele in header:\n                    header_info_dic[ele] = header[ele]\n\n        result = None\n        if header_info_dic:\n            result = json.dumps(header_info_dic)\n\n        self.logger.debug(\n            \"Order._header_info_lookup() ended with: %s keys in dic\",\n            len(header_info_dic.keys()),\n        )\n        return result\n\n    def _finalize_csr(\n        self, order_name: str, payload: Dict[str, str], header: str = None\n    ) -> Tuple[int, str, str, str]:\n        \"\"\"Handle CSR finalization for an order\"\"\"\n        self.logger.debug(\"Order._finalize_csr(%s)\", order_name)\n\n        message = None\n        # lookup header information\n        header_info = self._header_info_lookup(header)\n        # this is a new request\n        (code, certificate_name, detail) = self._process_csr(\n            order_name, payload[\"csr\"], header_info\n        )\n        # change status only if we do not have a poll_identifier (stored in detail variable)\n        if code == 200:\n            if not detail:\n                # update order_status / set to valid\n                self.repository.order_update({\"name\": order_name, \"status\": \"valid\"})\n        elif certificate_name == \"timeout\":\n            code = 200\n            message = certificate_name\n        elif certificate_name == \"urn:ietf:params:acme:error:rejectedIdentifier\":\n            code = 401\n            message = certificate_name\n        else:\n            message = certificate_name\n            detail = \"enrollment failed\"\n\n        self.logger.debug(\"Order._finalize_csr() ended\")\n        return (code, message, detail, certificate_name)\n\n    def _finalize_order(\n        self, order_name: str, payload: Dict[str, str], header: str = None\n    ) -> Tuple[int, str, str, str]:\n        \"\"\"finalize request\"\"\"\n        self.logger.debug(\"Order._finalize_order()\")\n\n        certificate_name = None\n        message = None\n        detail = None\n\n        # lookup order-status (must be ready to proceed)\n        order_dic = self._get_order_info(order_name)\n        if \"status\" in order_dic and order_dic[\"status\"] == \"ready\":\n            # update order_status / set to processing\n            self.repository.order_update({\"name\": order_name, \"status\": \"processing\"})\n            if \"csr\" in payload:\n                (code, message, detail, certificate_name) = self._finalize_csr(\n                    order_name, payload, header\n                )\n            else:\n                code = 400\n                message = self.error_msg_dic[\"badcsr\"]\n                detail = \"csr is missing in payload\"\n        elif (\n            \"status\" in order_dic\n            and order_dic[\"status\"] == \"valid\"\n            and self.config.idempotent_finalize\n        ):\n            self.logger.debug(\n                \"Order._finalize_order(): kind of polling request - order is already valid - lookup certificate\"\n            )\n            code = 200\n            try:\n                cert_dic = self.repository.certificate_lookup(\"order__name\", order_name)\n            except Exception as err_:\n                self.logger.critical(\n                    \"Database error: Certificate lookup failed: %s\", err_\n                )\n                cert_dic = {}\n            if cert_dic and \"name\" in cert_dic:\n                certificate_name = cert_dic[\"name\"]\n        else:\n            code = 403\n            message = self.error_msg_dic[\"ordernotready\"]\n            detail = \"Order is not ready\"\n\n        self.logger.debug(\"Order._finalize_order() ended\")\n        return (code, message, detail, certificate_name)\n\n    def _process_order_request(\n        self,\n        order_name: str,\n        protected: Dict[str, str],\n        payload: Dict[str, str],\n        header: Optional[str] = None,\n    ) -> Tuple[int, str, str, str]:\n        \"\"\"process order\"\"\"\n        self.logger.debug(\"Order._process_order_request({%s)\", order_name)\n\n        certificate_name = None\n        message = None\n        detail = None\n\n        if \"url\" in protected:\n            if \"finalize\" in protected[\"url\"]:\n                (code, message, detail, certificate_name) = self._finalize_order(\n                    order_name, payload, header\n                )\n            else:\n                self.logger.debug(\"polling request()\")\n                code = 200\n                try:\n                    cert_dic = self.repository.certificate_lookup(\n                        \"order__name\", order_name\n                    )\n                except Exception as err_:\n                    self.logger.critical(\n                        \"Database error: Certificate lookup failed: %s\", err_\n                    )\n                    cert_dic = {}\n                if cert_dic and \"name\" in cert_dic:\n                    certificate_name = cert_dic[\"name\"]\n        else:\n            code = 400\n            message = self.error_msg_dic[\"malformed\"]\n            detail = \"url is missing in protected\"\n\n        self.logger.debug(\n            \"Order._process_order_request() ended with order:%s %s:%s:%s\",\n            order_name,\n            code,\n            message,\n            detail,\n        )\n        return (code, message, detail, certificate_name)\n\n    def _process_csr(\n        self, order_name: str, csr: str, header_info: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"process certificate signing request\"\"\"\n        self.logger.debug(\"Order._process_csr(%s)\", order_name)\n\n        order_dic = self._get_order_info(order_name)\n        if order_dic:\n            # change decoding from b64url to b64\n            csr = b64_url_recode(self.logger, csr)\n\n            with Certificate(self.debug, self.server_name, self.logger) as certificate:\n                certificate_name = certificate.store_csr(order_name, csr, header_info)\n                if certificate_name:\n                    (error, detail) = certificate.enroll_and_store(\n                        certificate_name, csr, order_name\n                    )\n                    if not error:\n                        code = 200\n                        message = certificate_name\n                    elif error == \"urn:ietf:params:acme:error:rejectedIdentifier\":\n                        code = 401\n                        message = error\n                    else:\n                        code = 400\n                        message = error\n                        if message == self.error_msg_dic[\"serverinternal\"]:\n                            code = 500\n                else:\n                    code = 500\n                    message = self.error_msg_dic[\"serverinternal\"]\n                    detail = \"CSR processing failed\"\n        else:\n            code = 400\n            message = self.error_msg_dic[\"unauthorized\"]\n            detail = f\"order: {order_name} not found\"\n\n        self.logger.debug(\n            \"Order._process_csr() ended with order:%s %s:{%s:%s\",\n            order_name,\n            code,\n            message,\n            detail,\n        )\n        return (code, message, detail)\n\n    def _order_dic_create(self, tmp_dic: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"create order dictionary\"\"\"\n        self.logger.debug(\"Order._order_dic_create()\")\n\n        order_dic = {}\n        if \"status\" in tmp_dic:\n            order_dic[\"status\"] = tmp_dic[\"status\"]\n        if \"expires\" in tmp_dic:\n            order_dic[\"expires\"] = uts_to_date_utc(tmp_dic[\"expires\"])\n        if \"notbefore\" in tmp_dic and tmp_dic[\"notbefore\"] != 0:\n            order_dic[\"notBefore\"] = uts_to_date_utc(tmp_dic[\"notbefore\"])\n        if \"notafter\" in tmp_dic and tmp_dic[\"notafter\"] != 0:\n            order_dic[\"notAfter\"] = uts_to_date_utc(tmp_dic[\"notafter\"])\n        if \"identifiers\" in tmp_dic:\n            try:\n                order_dic[\"identifiers\"] = json.loads(tmp_dic[\"identifiers\"])\n            except Exception:\n                self.logger.error(\n                    \"Error while parsing the identifier %s\",\n                    tmp_dic[\"identifiers\"],\n                )\n\n        self.logger.debug(\"Order._order_dic_create() ended\")\n        return order_dic\n\n    def _get_authorization_list(self, order_name: str) -> List[str]:\n        \"\"\"Lookup authorization list. Returns list or empty list on error.\"\"\"\n        self.logger.debug(\"Order._get_authorization_list(%s)\", order_name)\n        try:\n            authz_list = self.repository.authorization_lookup(\n                \"order__name\", order_name, [\"name\", \"status__name\"]\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to look up authorization list: %s\", err_\n            )\n            authz_list = []\n        self.logger.debug(\"Order._get_authorization_list() ended\")\n        return authz_list\n\n    def _update_validity_list(\n        self, authz_list: List[str], order_dic: Dict[str, str], order_name: str\n    ):\n        \"\"\"update validity list and order status\"\"\"\n        self.logger.debug(\"Order._update_validity_list()\")\n        validity_list = []\n        for authz in authz_list:\n            if \"name\" in authz:\n                order_dic[\"authorizations\"].append(\n                    f'{self.server_name}{self.path_dic[\"authz_path\"]}{authz[\"name\"]}'\n                )\n            if \"status__name\" in authz:\n                if authz[\"status__name\"] == \"valid\":\n                    validity_list.append(True)\n                else:\n                    validity_list.append(False)\n\n        # update orders status from pending to ready\n        if validity_list and \"status\" in order_dic:\n            if False not in validity_list and order_dic[\"status\"] == \"pending\":\n                self.repository.order_update({\"name\": order_name, \"status\": \"ready\"})\n\n        self.logger.debug(\"Order.get_order_details() ended\")\n\n    def get_order_details(self, order_name: str) -> Dict[str, str]:\n        \"\"\"Show order details based on order name.\"\"\"\n        self.logger.debug(\"Order.get_order_details(%s)\", order_name)\n\n        order_dic = {}\n        tmp_dic = self._get_order_info(order_name)\n        if tmp_dic:\n            # create order dictionary and lookup authorization list\n            order_dic = self._order_dic_create(tmp_dic)\n            authz_list = self._get_authorization_list(order_name)\n            if authz_list:\n                order_dic[\"authorizations\"] = []\n                # collect status of different authorizations in list and update order status\n                self._update_validity_list(authz_list, order_dic, order_name)\n        self.logger.debug(\"Order.get_order_details() ended\")\n        return order_dic\n\n    def invalidate_expired_orders(\n        self, timestamp: int = None\n    ) -> Tuple[List[str], List[str]]:\n        \"\"\"Invalidate orders that have expired.\"\"\"\n        self.logger.debug(\"Order.invalidate_expired_orders(%s)\", timestamp)\n\n        if not timestamp:\n            timestamp = uts_now()\n            self.logger.debug(\n                \"Order.invalidate_expired_orders(): set timestamp to %s\", timestamp\n            )\n\n        field_list = [\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"identifiers\",\n            \"created_at\",\n            \"status__id\",\n            \"status__name\",\n            \"account__id\",\n            \"account__name\",\n            \"account__contact\",\n        ]\n        try:\n            order_list = self.repository.orders_invalid_search(\n                \"expires\", timestamp, vlist=field_list, operant=\"<=\"\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to search for expired orders: %s\", err_\n            )\n            order_list = []\n        output_list = []\n        for order in order_list:\n            # select all orders which are not invalid\n            if (\n                \"name\" in order\n                and \"status__name\" in order\n                and order[\"status__name\"] != \"invalid\"\n            ):\n                # change status and add to output list\n                output_list.append(order)\n                data_dic = {\"name\": order[\"name\"], \"status\": \"invalid\"}\n                try:\n                    self.repository.order_update(data_dic)\n                except Exception as err_:\n                    self.logger.critical(\n                        \"Database error: failed to update order status to invalid: %s\",\n                        err_,\n                    )\n\n        self.logger.debug(\n            \"Order.invalidate_expired_orders() ended: %s orders identified\",\n            len(output_list),\n        )\n        return (field_list, output_list)\n\n    def create_from_content(self, content: str) -> Dict[str, str]:\n        \"\"\"new order request (renamed from new)\"\"\"\n        self.logger.debug(\"Order.create_from_content()\")\n\n        response_dic = {}\n        # check message\n        (code, message, detail, _protected, payload, account_name) = self.message.check(\n            content\n        )\n\n        if code == 200:\n            (error, detail, order_name, auth_dic, expires) = self.create_order(\n                payload, account_name\n            )\n            if not error:\n                code = 201\n                response_dic[\"header\"] = {}\n                response_dic[\"header\"][\n                    \"Location\"\n                ] = f'{self.server_name}{self.path_dic[\"order_path\"]}{order_name}'\n                response_dic[\"data\"] = {}\n                response_dic[\"data\"][\"identifiers\"] = []\n                response_dic[\"data\"][\"authorizations\"] = []\n                response_dic[\"data\"][\"status\"] = \"pending\"\n                response_dic[\"data\"][\"expires\"] = expires\n                response_dic[\"data\"][\n                    \"finalize\"\n                ] = f'{self.server_name}{self.path_dic[\"order_path\"]}{order_name}/finalize'\n                for auth_name, value in auth_dic.items():\n                    response_dic[\"data\"][\"authorizations\"].append(\n                        f'{self.server_name}{self.path_dic[\"authz_path\"]}{auth_name}'\n                    )\n                    response_dic[\"data\"][\"identifiers\"].append(value)\n            elif error in [\n                self.error_msg_dic[\"rejectedidentifier\"],\n                self.error_msg_dic[\"invalidprofile\"],\n            ]:\n                code = 403\n                message = error\n                if not detail:\n                    detail = \"Some of the requested identifiers got rejected\"\n            elif error == self.error_msg_dic[\"malformed\"]:\n                code = 400\n                message = error\n                if not detail:\n                    detail = \"One of the requested identifiers is not supported\"\n            else:\n                code = 400\n                message = error\n                detail = \"Could not process order\"\n\n        # prepare/enrich response\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        response_dic = self.message.prepare_response(response_dic, status_dic)\n\n        self.logger.debug(\n            \"Order.create_from_content() returns: %s\", json.dumps(response_dic)\n        )\n        return response_dic\n\n    def _parse_order_message(\n        self, protected: Dict[str, str], payload: Dict[str, str], header: str = None\n    ) -> Tuple[int, str, str, str, str]:\n        \"\"\"parse new order message\"\"\"\n        self.logger.debug(\"Order._parse_order_message()\")\n\n        order_name = certificate_name = None\n\n        if \"url\" in protected:\n            order_name = self._name_get(protected[\"url\"])\n            if order_name:\n                order_dic = self.get_order_details(order_name)\n                if order_dic:\n                    (\n                        code,\n                        message,\n                        detail,\n                        certificate_name,\n                    ) = self._process_order_request(\n                        order_name, protected, payload, header\n                    )\n                else:\n                    code = 403\n                    message = self.error_msg_dic[\"ordernotready\"]\n                    detail = \"order not found\"\n            else:\n                code = 400\n                message = self.error_msg_dic[\"malformed\"]\n                detail = \"order name is missing\"\n        else:\n            code = 400\n            message = self.error_msg_dic[\"malformed\"]\n            detail = \"url is missing in protected\"\n\n        self.logger.debug(\"Order._parse_order_message() ended with code: %s\", code)\n        return (code, message, detail, certificate_name, order_name)\n\n    def parse_order_content(self, content: str, header: str = None) -> Dict[str, str]:\n        \"\"\"parse order request (renamed from parse)\"\"\"\n        self.logger.debug(\"Order.parse_order_content()\")\n\n        # invalidate expired orders\n        if not self.config.expiry_check_disable:\n            self.invalidate_expired_orders()\n\n        response_dic = {}\n        # check message\n        (code, message, detail, protected, payload, _account_name) = self.message.check(\n            content\n        )\n\n        if code == 200:\n            # parse message\n            (\n                code,\n                message,\n                detail,\n                certificate_name,\n                order_name,\n            ) = self._parse_order_message(protected, payload, header)\n\n            if code == 200:\n                # create response\n                response_dic[\"header\"] = {}\n                response_dic[\"header\"][\n                    \"Location\"\n                ] = f'{self.server_name}{self.path_dic[\"order_path\"]}{order_name}'\n                response_dic[\"data\"] = self.get_order_details(order_name)\n                if (\n                    \"status\" in response_dic[\"data\"]\n                    and response_dic[\"data\"][\"status\"] == \"processing\"\n                ):\n                    # set retry header as cert issuane is not completed.\n                    response_dic[\"header\"][\"Retry-After\"] = f\"{self.config.retry_after}\"\n                response_dic[\"data\"][\n                    \"finalize\"\n                ] = f'{self.server_name}{self.path_dic[\"order_path\"]}{order_name}/finalize'\n                # add the path to certificate if order-status is ready\n                if (\n                    certificate_name\n                    and \"status\" in response_dic[\"data\"]\n                    and response_dic[\"data\"][\"status\"] == \"valid\"\n                ):\n                    response_dic[\"data\"][\n                        \"certificate\"\n                    ] = f'{self.server_name}{self.path_dic[\"cert_path\"]}{certificate_name}'\n\n        # prepare/enrich response\n        status_dic = {\"code\": code, \"type\": message, \"detail\": detail}\n        response_dic = self.message.prepare_response(response_dic, status_dic)\n\n        self.logger.debug(\n            \"Order.parse_order_content() returns: %s\", json.dumps(response_dic)\n        )\n        return response_dic\n\n    # === Legacy API Compatibility ===\n\n    def invalidate(self, timestamp: int = None) -> Tuple[List[str], List[str]]:\n        \"\"\"invalidate orders\"\"\"\n        self.logger.debug(\n            \"Order.invalidate() - Compatibility wrapper for old method name\"\n        )\n        return self.invalidate_expired_orders(timestamp)\n\n    def new(self, content: str) -> Dict[str, str]:\n        \"\"\"new order request\"\"\"\n        self.logger.debug(\"Order.new() - Compatibility wrapper for old method name\")\n        return self.create_from_content(content)\n\n    def parse(self, content: str, header: str = None) -> Dict[str, str]:\n        \"\"\"parse order request\"\"\"\n        self.logger.debug(\"Order.parse() - Compatibility wrapper for old method name\")\n        return self.parse_order_content(content, header)\n"
  },
  {
    "path": "acme_srv/renewalinfo.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Renewalinfo class: ACME renewal info handler with separated config and repository helpers.\"\"\"\nfrom __future__ import print_function\nfrom typing import Dict\nfrom dataclasses import dataclass\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.message import Message\nfrom acme_srv.helper import (\n    string_sanitize,\n    certid_hex_get,\n    uts_to_date_utc,\n    error_dic_get,\n    load_config,\n    ca_handler_load,\n    uts_now,\n    cert_serial_get,\n    cert_aki_get,\n    b64_url_recode,\n    b64_decode,\n)\n\n\n@dataclass\nclass RenewalinfoConfig:\n    \"\"\"configuration dataclass for Renewalinfo handler\"\"\"\n\n    renewal_force: bool = False\n    renewalthreshold_pctg: float = 85.0\n    retry_after_timeout: int = 86400\n    renewalinfo_lookup: bool = False\n\n\nclass RenewalinfoRepository:\n    \"\"\"Renewalinfo repository helper with database access methods.\"\"\"\n\n    def __init__(self, dbstore, logger):\n        self.dbstore = dbstore\n        self.logger = logger\n\n    def get_certificate_by_certid(self, certid_hex):\n        \"\"\"Retrieve certificate by certid from database.\"\"\"\n        self.logger.debug(\n            \"RenewalinfoRepository.get_certificate_by_certid(%s)\", certid_hex\n        )\n        try:\n            return self.dbstore.certificate_lookup(\n                \"renewal_info\",\n                certid_hex,\n                (\n                    \"id\",\n                    \"name\",\n                    \"cert\",\n                    \"cert_raw\",\n                    \"expire_uts\",\n                    \"issue_uts\",\n                    \"created_at\",\n                ),\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to look up certificate for renewal info (draft01): %s\",\n                err_,\n            )\n            return None\n\n    def get_certificates_by_serial(self, serial):\n        \"\"\"Retrieve certificates by serial from database.\"\"\"\n        self.logger.debug(\n            \"RenewalinfoRepository.get_certificates_by_serial(%s)\", serial\n        )\n        try:\n            return self.dbstore.certificates_search(\n                \"serial\",\n                serial,\n                operant=\"is\",\n                vlist=[\n                    \"id\",\n                    \"name\",\n                    \"cert\",\n                    \"cert_raw\",\n                    \"expire_uts\",\n                    \"issue_uts\",\n                    \"aki\",\n                    \"created_at\",\n                ],\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to look up certificate for renewal info (draft02): %s\",\n                err_,\n            )\n            return []\n\n    def add_certificate(self, data_dic):\n        \"\"\"Add or update certificate in database.\"\"\"\n        self.logger.debug(\"RenewalinfoRepository.add_certificate()\")\n        return self.dbstore.certificate_add(data_dic)\n\n    def get_housekeeping_param(self, name):\n        \"\"\"Retrieve housekeeping parameter by name from database.\"\"\"\n        self.logger.debug(\"RenewalinfoRepository.get_housekeeping_param(%s)\", name)\n        return self.dbstore.hkparameter_get(name)\n\n    def add_housekeeping_param(self, param):\n        \"\"\"Add or update housekeeping parameter in database.\"\"\"\n        self.logger.debug(\"RenewalinfoRepository.add_housekeeping_param()\")\n        return self.dbstore.hkparameter_add(param)\n\n\nclass Renewalinfo(object):\n    \"\"\"Renewalinfo handler with business logic, config, and repository helpers.\"\"\"\n\n    def __init__(\n        self, debug: bool = False, srv_name: str = None, logger: object = None\n    ):\n        self.debug = debug\n        self.logger = logger\n        self.server_name = srv_name\n        self.path_dic = {\"renewalinfo\": \"/acme/renewal-info/\"}\n        self.dbstore = DBstore(self.debug, self.logger)\n        self.message = Message(self.debug, self.server_name, self.logger)\n        self.err_msg_dic = error_dic_get(self.logger)\n        self.config = RenewalinfoConfig()\n        self.repository = RenewalinfoRepository(self.dbstore, self.logger)\n        self.cahandler = None\n\n    def _load_configuration(self):\n        \"\"\"Load renewalinfo configuration from file (harmonized approach)\"\"\"\n        self.logger.debug(\"Renewalinfo._load_configuration()\")\n\n        config_dic = load_config()\n\n        if \"Renewalinfo\" in config_dic:\n            try:\n                self.config.renewal_force = config_dic.getboolean(\n                    \"Renewalinfo\", \"renewal_force\", fallback=False\n                )\n            except Exception as err_:\n                self.logger.error(\"renewal_force parsing error: %s\", err_)\n                self.config.renewal_force = False\n            try:\n                self.config.renewalthreshold_pctg = float(\n                    config_dic.get(\n                        \"Renewalinfo\", \"renewalthreshold_pctg\", fallback=85.0\n                    )\n                )\n            except Exception as err_:\n                self.logger.error(\"renewalthreshold_pctg parsing error: %s\", err_)\n                self.config.renewalthreshold_pctg = 85.0\n            try:\n                self.config.retry_after_timeout = int(\n                    config_dic.get(\"Renewalinfo\", \"retry_after_timeout\", fallback=86400)\n                )\n            except Exception as err_:\n                self.logger.error(\"retry_after_timeout parsing error: %s\", err_)\n                self.config.retry_after_timeout = 86400\n\n        self._load_ca_handler(config_dic)\n        self._parse_cahandler_section(config_dic)\n\n        self.logger.debug(\"Renewalinfo._load_configuration() ended.\")\n\n    def _parse_cahandler_section(self, config_dic: object) -> None:\n        \"\"\"Parse the [CAHandler] section for ACME URL and profile sync settings.\"\"\"\n        self.logger.debug(\"Directory._parse_cahandler_section()\")\n        if \"CAhandler\" in config_dic:\n            cfg_dic = dict(config_dic[\"CAhandler\"])\n            self.config.acme_url = cfg_dic.get(\"acme_url\", None)\n\n            try:\n                self.config.renewalinfo_lookup = config_dic.getboolean(\n                    \"CAhandler\", \"renewalinfo_lookup\", fallback=False\n                )\n            except Exception as err_:\n                self.logger.error(\"renewalinfo_lookup parsing error: %s\", err_)\n                self.config.renewalinfo_lookup = False\n\n        if self.config.renewalinfo_lookup and not self.config.acme_url:\n            self.logger.error(\"CAhandler section incomplete for renewalinfo lookup\")\n            self.config.renewalinfo_lookup = False\n\n        self.logger.debug(\"Directory._parse_cahandler_section() ended\")\n\n    def _load_ca_handler(self, config_dic: object) -> None:\n        \"\"\"Load the CA handler module as configured.\"\"\"\n        ca_handler_module = ca_handler_load(self.logger, config_dic)\n        if ca_handler_module:\n            self.cahandler = ca_handler_module.CAhandler\n        else:\n            self.logger.critical(\"No ca_handler loaded\")\n\n    def __enter__(self):\n        self._load_configuration()\n        return self\n\n    def __exit__(self, *args):\n        pass\n\n    # --- Business logic methods ---\n\n    def _lookup_certificate_by_renewalinfo(\n        self, renewalinfo_string: str\n    ) -> Dict[str, str]:\n        self.logger.debug(\n            \"Renewalinfo._lookup_certificate_by_renewalinfo(%s)\", renewalinfo_string\n        )\n        if \".\" in renewalinfo_string:\n            serial, aki = self._extract_serial_and_aki_from_string(renewalinfo_string)\n            cert_dic = self._lookup_certificate_by_serial_and_aki(serial, aki)\n        else:\n            _mda, certid_hex = certid_hex_get(self.logger, renewalinfo_string)\n            cert_dic = self._lookup_certificate_by_certid(certid_hex)\n        self.logger.debug(\n            \"Renewalinfo._lookup_certificate_by_renewalinfo(%s) - ended with: %s\",\n            renewalinfo_string,\n            bool(cert_dic),\n        )\n        return cert_dic\n\n    def _update_certificate_table_with_serial_and_aki(self):\n        self.logger.debug(\"Renewalinfo._update_certificate_table_with_serial_and_aki()\")\n        try:\n            certificate_list = self.dbstore.certificates_search(\n                \"serial\",\n                None,\n                operant=\"is\",\n                vlist=[\"id\", \"name\", \"cert\", \"cert_raw\", \"serial\", \"aki\"],\n            )\n        except Exception as err_:\n            self.logger.critical(\n                \"Database error: failed to retrieve certificate list for renewal info update: %s\",\n                err_,\n            )\n            certificate_list = []\n        update_cnt = 0\n        for cert in certificate_list:\n            if (\n                \"cert_raw\" in cert\n                and cert[\"cert_raw\"]\n                and \"name\" in cert\n                and \"cert\" in cert\n            ):\n                serial = cert_serial_get(self.logger, cert[\"cert_raw\"], hexformat=True)\n                aki = cert_aki_get(self.logger, cert[\"cert_raw\"])\n                data_dic = {\n                    \"serial\": serial,\n                    \"aki\": aki,\n                    \"name\": cert[\"name\"],\n                    \"cert_raw\": cert[\"cert_raw\"],\n                    \"cert\": cert[\"cert\"],\n                }\n                self.repository.add_certificate(data_dic)\n                update_cnt += 1\n        self.logger.debug(\n            \"Renewalinfo._update_certificate_table_with_serial_and_aki(%s) - done\",\n            update_cnt,\n        )\n\n    def _lookup_certificate_by_certid(self, certid_hex: str) -> Dict[str, str]:\n        self.logger.debug(\"Renewalinfo._lookup_certificate_by_certid()\")\n        return self.repository.get_certificate_by_certid(certid_hex)\n\n    def _lookup_certificate_by_serial_and_aki(\n        self, serial: str, aki: str\n    ) -> Dict[str, str]:\n        self.logger.debug(\"Renewalinfo._lookup_certificate_by_serial_and_aki()\")\n        cert_dic = {}\n        cert_list = self.repository.get_certificates_by_serial(serial)\n        if not cert_list and serial and serial.startswith(\"0\"):\n            cert_list = self.repository.get_certificates_by_serial(serial.lstrip(\"0\"))\n        for cert in cert_list:\n            if cert.get(\"aki\") == aki:\n                cert_dic = cert\n                break\n        self.logger.debug(\n            \"Renewalinfo._lookup_certificate_by_serial_and_aki() ended with: %s\",\n            bool(cert_dic),\n        )\n        return cert_dic\n\n    def _generate_renewalinfo_window(self, cert_dic: Dict[str, str]) -> Dict[str, str]:\n        self.logger.debug(\"Renewalinfo._generate_renewalinfo_window()\")\n        if \"expire_uts\" in cert_dic and cert_dic[\"expire_uts\"]:\n            if \"issue_uts\" not in cert_dic or not cert_dic[\"issue_uts\"]:\n                cert_dic[\"issue_uts\"] = uts_now()\n            if self.config.renewal_force:\n                self.logger.debug(\"Renewalinfo.get() - force renewal\")\n                cert_dic[\"expire_uts\"] = uts_now() + 86400\n                start_uts = int(cert_dic[\"expire_uts\"] - (365 * 86400))\n            else:\n                start_uts = (\n                    int(\n                        (cert_dic[\"expire_uts\"] - cert_dic[\"issue_uts\"])\n                        * self.config.renewalthreshold_pctg\n                        / 100\n                    )\n                    + cert_dic[\"issue_uts\"]\n                )\n            renewalinfo_dic = {\n                \"suggestedWindow\": {\n                    \"start\": uts_to_date_utc(start_uts),\n                    \"end\": uts_to_date_utc(cert_dic[\"expire_uts\"]),\n                }\n            }\n        else:\n            renewalinfo_dic = {}\n        self.logger.debug(\"Renewalinfo._generate_renewalinfo_window() ended\")\n        return renewalinfo_dic\n\n    def _get_renewalinfo_data(self, renewalinfo_string: str) -> Dict[str, str]:\n        self.logger.debug(\"Renewalinfo._get_renewalinfo_data()\")\n        cert_dic = self._lookup_certificate_by_renewalinfo(renewalinfo_string)\n        renewalinfo_dic = self._generate_renewalinfo_window(cert_dic)\n        self.logger.debug(\n            \"Renewalinfo._get_renewalinfo_data() ended with: %s\", renewalinfo_dic\n        )\n        return renewalinfo_dic\n\n    def _parse_renewalinfo_string_from_url(self, url: str) -> str:\n        self.logger.debug(\"Renewalinfo._parse_renewalinfo_string_from_url()\")\n        url = url.replace(\n            f'{self.server_name}{self.path_dic[\"renewalinfo\"].rstrip(\"/\")}', \"\"\n        )\n        url = url.lstrip(\"/\")\n        renewalinfo_string = string_sanitize(self.logger, url)\n        self.logger.debug(\n            \"Renewalinfo._parse_renewalinfo_string_from_url() - renewalinfo_string: %s\",\n            renewalinfo_string,\n        )\n        return renewalinfo_string\n\n    def _extract_serial_and_aki_from_string(\n        self, renewalinfo_string: str\n    ) -> (str, str):\n        self.logger.debug(\"Renewalinfo._extract_serial_and_aki_from_string()\")\n        renewalinfo_list = renewalinfo_string.split(\".\")\n        if len(renewalinfo_list) == 2:\n            serial = b64_decode(\n                self.logger, b64_url_recode(self.logger, renewalinfo_list[1])\n            ).hex()\n            aki = b64_decode(\n                self.logger, b64_url_recode(self.logger, renewalinfo_list[0])\n            ).hex()\n        else:\n            serial = None\n            aki = None\n        self.logger.debug(\n            \"Renewalinfo._extract_serial_and_aki_from_string() - serial: %s, aki: %s\",\n            serial,\n            aki,\n        )\n        return (serial, aki)\n\n    def get(self, url: str) -> Dict[str, str]:\n        \"\"\"Get renewal information (backwards compatible public method)\"\"\"\n        self.logger.debug(\"Renewalinfo.get()\")\n\n        renewalinfo_string = self._parse_renewalinfo_string_from_url(url)\n        if self.config.renewalinfo_lookup and hasattr(\n            self.cahandler, \"lookup_renewalinfo\"\n        ):\n            with self.cahandler(None, self.logger) as ca_handler:\n                # get renewal info from CA handler\n                rc_code, renewalinfo_dic = ca_handler.lookup_renewalinfo(\n                    self.config.acme_url, renewalinfo_string\n                )\n        else:\n            # ensure serial and aki fields are populated in certificate table\n            if not self.repository.get_housekeeping_param(\"cert_aki_serial_update\"):\n                self._update_certificate_table_with_serial_and_aki()\n                self.logger.debug(\"Renewalinfo.get() - update housekeeping\")\n                self.repository.add_housekeeping_param(\n                    {\"name\": \"cert_aki_serial_update\", \"value\": True}\n                )\n\n            try:\n                renewalinfo_dic = self._get_renewalinfo_data(renewalinfo_string)\n                rc_code = 200 if renewalinfo_dic else 404\n            except Exception as err_:\n                self.logger.error(\"Error when getting renewal information: %s\", err_)\n                renewalinfo_dic = {}\n                rc_code = 400\n        response_dic = {\"code\": rc_code}\n        if renewalinfo_dic:\n            response_dic[\"data\"] = renewalinfo_dic\n            response_dic[\"header\"] = {\n                \"Retry-After\": f\"{self.config.retry_after_timeout}\"\n            }\n        else:\n            response_dic[\"data\"] = self.err_msg_dic[\"malformed\"]\n        return response_dic\n\n    def update(self, content: str) -> Dict[str, str]:\n        \"\"\"Update renewal info (backwards compatible public method)\"\"\"\n        self.logger.debug(\"Renewalinfo.update()\")\n        (\n            code,\n            _message,\n            _detail,\n            _protected,\n            payload,\n            _account_name,\n        ) = self.message.check(content)\n        response_dic = {}\n        if code == 200 and \"certid\" in payload and \"replaced\" in payload:\n            cert_dic = self._lookup_certificate_by_renewalinfo(payload[\"certid\"])\n            if cert_dic and payload[\"replaced\"]:\n                cert_dic[\"replaced\"] = True\n                cert_id = self.repository.add_certificate(cert_dic)\n                response_dic[\"code\"] = 200 if cert_id else 400\n            else:\n                response_dic[\"code\"] = 400\n        else:\n            response_dic[\"code\"] = 400\n        return response_dic\n"
  },
  {
    "path": "acme_srv/signature.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Signature class\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple, Dict, Optional\nfrom acme_srv.helper import signature_check, load_config, error_dic_get\nfrom acme_srv.db_handler import DBstore\n\n\nclass Signature:\n    \"\"\"Handles signature verification and key loading for ACME accounts.\"\"\"\n\n    def __init__(\n        self, debug: bool = False, srv_name: str = None, logger: object = None\n    ):\n        self.debug = debug\n        self.logger = logger\n        self.dbstore = DBstore(self.debug, self.logger)\n        self.err_msg_dic = error_dic_get(self.logger)\n        self.server_name = srv_name\n        cfg = load_config()\n        self.revocation_path = self._get_revocation_path(cfg)\n\n    def _get_revocation_path(self, cfg) -> str:\n        if \"Directory\" in cfg and \"url_prefix\" in cfg[\"Directory\"]:\n            return cfg[\"Directory\"][\"url_prefix\"] + \"/acme/revokecert\"\n        return \"/acme/revokecert\"\n\n    def _jwk_loader(self, kid, cli: bool = False) -> Optional[Dict[str, str]]:\n        \"\"\"Load JWK for a specific account id, optionally using CLI method.\"\"\"\n        method = self.dbstore.cli_jwk_load if cli else self.dbstore.jwk_load\n        self.logger.debug(f\"Signature._jwk_loader({kid}, cli={cli})\")\n        try:\n            return method(kid)\n        except Exception as err_:\n            self.logger.critical(\n                f\"Database error: failed to load {'CLI ' if cli else ''}JWK for account id {kid}: {err_}\"\n            )\n            return None\n\n    def cli_check(self, aname: str, content: str) -> Tuple[bool, str, None]:\n        \"\"\"Check signature against CLI key for account.\"\"\"\n        self.logger.debug(f\"Signature.cli_check({aname})\")\n        if not content:\n            return (False, self.err_msg_dic[\"malformed\"], None)\n        if not aname:\n            return (False, self.err_msg_dic[\"accountdoesnotexist\"], None)\n        pub_key = self._jwk_loader(aname, cli=True)\n        if not pub_key:\n            return (False, self.err_msg_dic[\"accountdoesnotexist\"], None)\n        result, error = signature_check(self.logger, content, pub_key)\n        self.logger.debug(f\"Signature.cli_check() ended with: {result}:{error}\")\n        return (result, error, None)\n\n    def check(\n        self,\n        aname: str,\n        content: str,\n        use_emb_key: bool = False,\n        protected: Dict[str, str] = None,\n    ) -> Tuple[bool, str, None]:\n        \"\"\"Check signature against account key or embedded JWK.\"\"\"\n        self.logger.debug(f\"Signature.check({aname})\")\n        if not content:\n            return (False, self.err_msg_dic[\"malformed\"], None)\n        error = None\n        if aname:\n            pub_key = self._jwk_loader(aname)\n            if not pub_key:\n                error = self.err_msg_dic[\"accountdoesnotexist\"]\n                return (False, error, None)\n            result, error = signature_check(self.logger, content, pub_key)\n            self.logger.debug(f\"Signature.check() ended with: {result}:{error}\")\n            return (result, error, None)\n        elif use_emb_key:\n            self.logger.debug(\n                \"Signature.check() check signature against key included in jwk\"\n            )\n            if protected and \"jwk\" in protected:\n                pub_key = protected[\"jwk\"]\n                result, error = signature_check(self.logger, content, pub_key)\n                self.logger.debug(f\"Signature.check() ended with: {result}:{error}\")\n                return (result, error, None)\n            else:\n                error = self.err_msg_dic[\"accountdoesnotexist\"]\n                return (False, error, None)\n        else:\n            error = self.err_msg_dic[\"accountdoesnotexist\"]\n            return (False, error, None)\n\n    def eab_check(self, content: str, mac_key: str) -> Tuple[bool, str]:\n        \"\"\"Check signature for External Account Binding (EAB).\"\"\"\n        self.logger.debug(\"Signature.eab_check()\")\n        if not (content and mac_key):\n            return (False, self.err_msg_dic[\"malformed\"])\n        result, error = signature_check(self.logger, content, mac_key, json_=True)\n        self.logger.debug(f\"Signature.eab_check() ended with: {result}:{error}\")\n        return (result, error)\n"
  },
  {
    "path": "acme_srv/threadwithreturnvalue.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ThreadWithReturnValue class\"\"\"\n# pylint: disable=r0913\nfrom threading import Thread\n\n\nclass ThreadWithReturnValue(Thread):\n    \"\"\"main class\"\"\"\n\n    def __init__(\n        self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None\n    ):\n        Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)\n\n        self._return = None\n\n    def run(self):\n        if self._target is not None:\n            self._return = self._target(*self._args, **self._kwargs)\n\n    def join(self, timeout: int = None):\n        Thread.join(self, timeout=timeout)\n        return self._return\n"
  },
  {
    "path": "acme_srv/trigger.py",
    "content": "\"\"\"trigger class\"\"\"\n\n# pylint: disable=c0209\nfrom __future__ import print_function\nimport json\nfrom typing import List, Tuple, Dict\nfrom acme_srv.certificate import Certificate\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.helper import (\n    convert_byte_to_string,\n    cert_pubkey_get,\n    csr_pubkey_get,\n    cert_der2pem,\n    b64_decode,\n    load_config,\n    ca_handler_load,\n)\n\n\nclass Trigger(object):\n    \"\"\"Challenge handler\"\"\"\n\n    def __init__(\n        self, debug: bool = False, srv_name: str = None, logger: object = None\n    ):\n        self.debug = debug\n        self.server_name = srv_name\n        self.cahandler = None\n        self.logger = logger\n        self.dbstore = DBstore(debug, self.logger)\n        self.tnauthlist_support = False\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"close the connection at the end of the context\"\"\"\n\n    def _certname_lookup(self, cert_pem: str) -> List[str]:\n        \"\"\"compared certificate against csr stored in db\"\"\"\n        self.logger.debug(\"Trigger._certname_lookup()\")\n\n        result_list = []\n        # extract the public key form certificate\n        cert_pubkey = cert_pubkey_get(self.logger, cert_pem)\n        with Certificate(self.debug, \"foo\", self.logger) as certificate:\n            # search certificates in status \"processing\"\n            cert_list = certificate.certlist_search(\n                \"order__status_id\", 4, [\"name\", \"csr\", \"order__name\"]\n            )\n\n            for cert in cert_list:\n                # extract public key from certificate and compare it with pub from cert\n                if \"csr\" in cert and cert[\"csr\"]:\n                    csr_pubkey = csr_pubkey_get(self.logger, cert[\"csr\"])\n                    if csr_pubkey == cert_pubkey:\n                        result_list.append(\n                            {\n                                \"cert_name\": cert[\"name\"],\n                                \"order_name\": cert[\"order__name\"],\n                            }\n                        )\n        self.logger.debug(\"Trigger._certname_lookup() ended with: %s\", result_list)\n\n        return result_list\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"Certificate._config_load()\")\n        config_dic = load_config()\n        if \"Order\" in config_dic:\n            self.tnauthlist_support = config_dic.getboolean(\n                \"Order\", \"tnauthlist_support\", fallback=False\n            )\n\n        ca_handler_module = ca_handler_load(self.logger, config_dic)\n        if ca_handler_module:\n            # store handler in variable\n            try:\n                self.cahandler = ca_handler_module.CAhandler\n            except Exception as err_:\n                self.logger.critical(\n                    \"Failed to load CA handler module: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"ca_handler: %s\", ca_handler_module)\n        self.logger.debug(\"Certificate._config_load() ended.\")\n\n    def _cert_store(self, cert_bundle: str, cert_raw: str) -> Tuple[int, str, str]:\n        \"\"\"store certificate\"\"\"\n        self.logger.debug(\"Trigger._cert_store()\")\n\n        # returned cert_raw is in dear format, convert to pem to lookup the pubic key\n        cert_pem = convert_byte_to_string(\n            cert_der2pem(b64_decode(self.logger, cert_raw))\n        )\n\n        # lookup certificate_name by comparing public keys\n        cert_name_list = self._certname_lookup(cert_pem)\n\n        if cert_name_list:\n            for cert in cert_name_list:\n                data_dic = {\n                    \"cert\": cert_bundle,\n                    \"name\": cert[\"cert_name\"],\n                    \"cert_raw\": cert_raw,\n                }\n                try:\n                    self.dbstore.certificate_add(data_dic)\n                except Exception as err_:\n                    self.logger.critical(\n                        \"Database error: failed to add certificate during trigger processing: %s\",\n                        err_,\n                    )\n                if \"order_name\" in cert and cert[\"order_name\"]:\n                    try:\n                        # update order status to 5 (valid)\n                        self.dbstore.order_update(\n                            {\"name\": cert[\"order_name\"], \"status\": \"valid\"}\n                        )\n                    except Exception as err_:\n                        self.logger.critical(\n                            \"Database error: failed to update order status during trigger processing: %s\",\n                            err_,\n                        )\n            code = 200\n            message = \"OK\"\n            detail = None\n        else:\n            code = 400\n            message = \"certificate_name lookup failed\"\n            detail = None\n\n        self.logger.debug(\"Trigger._cert_store() ended\")\n        return (code, message, detail)\n\n    def _payload_process(self, payload: str) -> Tuple[int, str, str]:\n        \"\"\"process payload\"\"\"\n        self.logger.debug(\"Trigger._payload_process()\")\n        with self.cahandler(self.debug, self.logger) as ca_handler:\n            if payload:\n                (error, cert_bundle, cert_raw) = ca_handler.trigger(payload)\n                if cert_bundle and cert_raw:\n                    # store certificate and create responses\n                    (code, message, detail) = self._cert_store(cert_bundle, cert_raw)\n                else:\n                    code = 400\n                    message = error\n                    detail = None\n            else:\n                code = 400\n                message = \"payload malformed\"\n                detail = None\n\n        self.logger.debug(\"Trigger._payload_process() ended with: %s %s\", code, message)\n        return (code, message, detail)\n\n    def parse(self, content: str) -> Dict[str, str]:\n        \"\"\"new oder request\"\"\"\n        self.logger.debug(\"Trigger.parse()\")\n\n        # convert to json structure\n        try:\n            payload = json.loads(convert_byte_to_string(content))\n        except Exception:\n            payload = {}\n\n        if \"payload\" in payload:\n            if payload[\"payload\"]:\n                (code, message, detail) = self._payload_process(payload[\"payload\"])\n            else:\n                code = 400\n                message = \"malformed\"\n                detail = \"payload empty\"\n        else:\n            code = 400\n            message = \"malformed\"\n            detail = \"payload missing\"\n        response_dic = {}\n\n        # prepare/enrich response\n        response_dic[\"header\"] = {}\n        response_dic[\"code\"] = code\n        response_dic[\"data\"] = {\"status\": code, \"type\": message}\n        if detail:\n            response_dic[\"data\"][\"detail\"] = detail\n\n        self.logger.debug(\"Trigger.parse() returns: %s\", json.dumps(response_dic))\n        return response_dic\n"
  },
  {
    "path": "acme_srv/version.py",
    "content": "\"\"\"version file\"\"\"\n\n# Store the version here so:\n# 1) we don't load dependencies by storing it in __init__.py\n# 2) we can import it in setup.py for the same reason\n# 3) we can import it into your module module\n__version__ = \"0.42\"\n__dbversion__ = \"0.41\"\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title How to contribute to this project -->\n\n# Contributing\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue,\nemail, or any other method with the owners of this repository before making a change.\n\nPlease note we have a code of conduct, please follow it in all your interactions with the project.\n\n## Pull Request Process\n\n1. Ensure any install or build dependencies are removed before the end of the layer when doing a\n   build.\n1. Update the README.md with details of changes to the interface, this includes new environment\n   variables, exposed ports, useful file locations and container parameters.\n1. Increase the version numbers in any examples files and the README.md to the new version that this\n   Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).\n1. You may merge the Pull Request in once you have the sign-off of two other developers, or if you\n   do not have permission to do that, you may request the second reviewer to merge it for you.\n\n## Code of Conduct\n\n### Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n### Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n### Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n### Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at \\[INSERT EMAIL ADDRESS\\]. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,\navailable at [http://contributor-covenant.org/version/2/0][version]\n\n[homepage]: https://contributor-covenant.org\n[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/\n"
  },
  {
    "path": "docs/__init__.py",
    "content": "\"\"\"init.py\"\"\"\n\nfrom .version import __version__\n"
  },
  {
    "path": "docs/a2c-alma-loadbalancing.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title How to build an acme2certifier cluster on Alma Linux 9 -->\n\n# How to build an acme2certifier cluster on Alma Linux 9\n\nThis tutorial describes the configuration of a two-node acme2certifier cluster running in active/active configuration. Although both nodes are active at the same time and provide proxy services via different IP addresses, the database, configuration and and runtime objects will be replicated among the nodes.\n\nThis setup requires the switch to a different database engine as SQLite, which is the default a2c backend, is not designed to handle concurrent write access, which can happen in an active/active setup. Thus, [MariaDB](https://mariadb.org/) will be used. Configuration files and runtime objects will be replicated using [Lsyncd](https://github.com/lsyncd/lsyncd). The following diagram depicts the application stack to be used.\n\n![architecture](a2c-alma-loadbalancing.png \"architecture\")\n\nThe guide is written for **Alma Linux 9**, however adapting to other Linux distributions or Red Hat derivatives should not be difficult. There is also a guide for [Ubuntu 22.04](a2c-ubuntu-loadbalancing.md) available\n\n## Preparation\n\nTo set up the MariaDB Master-Master replication between multiple servers, you will need to ensure each system hostname is resolved to the correct IP address. I recommend setting up the FQDN in /etc/hosts on each server.\n\n```cfg\ncat /etc/hosts\n...\n192.168.14.136 alma9-c1.bar.local alma9-c1\n192.168.14.137 alma9-c2.bar.local alma9-c2\n```\n\nFurthermore, the EPEL-repository need to be enabled on both nodes.\n\n```bash\nsudo yum install -y epel-release\nsudo yum update -y\n```\n\nStop the firewalld on both nodes\n\n```bash\nsudo systemctl stop firewalld\nsudo systemctl disable firewalld\n```\n\n## Installation and configuration of MariaDB\n\nThe following instructions are based on [an existing tutorial](https://www.howtoforge.com/how-to-setup-mariadb-master-master-replication-on-debian-11/).\n\n### Setting up alma9-c1\n\n- install MariaDB-server\n\n```bash\nsudo yum install -y mariadb-server\n```\n\n- start MariaDB during startup\n\n```bash\nsudo systemctl enable mariadb\nsudo systemctl status mariadb\n```\n\n- Modify `/etc/my.cnf.d/mariadb-server.cnf`, change the IP binding, and add the following lines\n\n```cfg\n# listen on external address\nbind-address            = 192.168.14.136\n\nserver-id              = 1\nreport_host            = alma9-c1\n\nlog_bin                = /var/log/mariadb/mariadb-bin\nlog_bin_index          = /var/log/mariadb/mariadb-bin.index\n\nrelay_log              = /var/log/mariadb/relay-bin\nrelay_log_index        = /var/log/mariadb/relay-bin.index\n\n# avoiding primary key collision\nlog-slave-updates\nauto_increment_increment=2\nauto_increment_offset=1\n```\n\n- restart MariaDB-server\n\n```bash\nsudo systemctl restart mariadb\n```\n\n- verify service binding\n\n```bash\nss -plnt\nState Recv-Q Send-Q        Local Address:Port       Peer Address:Port   Process\n...\nLISTEN   0        80           192.168.14.136:3306            0.0.0.0:*       users:((\"mariadbd\",pid=815,fd=43))\n...\n```\n\n- open the MySQL command-line client\n\n```bash\nsudo mysql -u  root\n```\n\n- create the replication user\n\n```SQL\nCREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\nGRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\nFLUSH PRIVILEGES;\n```\n\n- Next, run the following query to check the current binary log and its exact position of it. In this example, the binary log file for the MariaDB server is \"mariadb-bin.000001\" with the position \"773\". These outputs will be used in the next stage for setting up the \"alma9-c2\" server.\n\n```SQL\nSHOW MASTER STATUS;\n```\n\n```SQL\n+--------------------+----------+--------------+------------------+\n| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |\n+--------------------+----------+--------------+------------------+\n| mariadb-bin.000001 |      773 |              |                  |\n+--------------------+----------+--------------+------------------+\n1 row in set (0.000 sec)\n```\n\n### Setting up alma9-c2\n\n- install MariaDB-server\n\n```bash\nsudo yum install -y mariadb-server\n```\n\n- start MariaDB during startup\n\n```bash\nsudo systemctl enable mariadb\nsudo systemctl status mariadb\n```\n\n- Modify `/etc/my.cnf.d/mariadb-server.cnf`, change the IP binding, and add the following lines\n\n```cfg\n# listen on external address\nbind-address            = 192.168.14.137\n\nserver-id              = 2\nreport_host            = alma9-c2\n\nlog_bin                = /var/log/mariadb/mariadb-bin\nlog_bin_index          = /var/log/mariadb/mariadb-bin.index\n\nrelay_log              = /var/log/mariadb/relay-bin\nrelay_log_index        = /var/log/mariadb/relay-bin.index\n\n# avoiding primary key collision\nlog-slave-updates\nauto_increment_increment=2\nauto_increment_offset=2\n```\n\n- restart MariaDB-server\n\n```bash\nsudo systemctl restart mariadb\n```\n\n- verify service binding\n\n```bash\nss -plnt\n```\n\n```bash\nState Recv-Q Send-Q        Local Address:Port       Peer Address:Port   Process\n...\nLISTEN   0        80           192.168.14.137:3306            0.0.0.0:*       users:((\"mariadbd\",pid=841,fd=41))\n...\n```\n\n- open the MySQL command-line client\n\n```bash\nsudo mysql -u  root\n```\n\n- create the replication user\n\n```SQL\nCREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\nGRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\nFLUSH PRIVILEGES;\n```\n\n- stop the slave and add information about the alma9-c1 master node as well as the binlog file name (\"mariadb-bin.000001\") and position (\"773\") from alma9-c1.\n\n```SQL\nSTOP SLAVE;\nCHANGE MASTER TO MASTER_HOST='alma9-c1', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='mariadb-bin.000001', MASTER_LOG_POS=773;\n```\n\n- start the slave again and verify the slave status on the \"alma9-c2\" server. You should get \"Slave_IO_Running: Yes\" and \"Slave_SQL_Running: Yes\",\n\n```SQL\nSTART SLAVE;\nSHOW SLAVE STATUS\\G\n```\n\n```SQL\n*************************** 1. row ***************************\n                Slave_IO_State: Waiting for master to send event\n                   Master_Host: alma9-c1\n...\n              Slave_IO_Running: Yes\n             Slave_SQL_Running: Yes\n...\n```\n\n### Configure master-master replication on alma9-c1\n\n- open the MySQL command-line client and create the replication user\n\n```bash\nsudo mysql -u  root\n```\n\n- stop the slave and add information about the alma9-c2 master node as well as the binlog file name and position.\n\n```SQL\nSTOP SLAVE;\nCHANGE MASTER TO MASTER_HOST='alma9-c2', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='mariadb-bin.000001', MASTER_LOG_POS=773;\n```\n\n- start the slave again and verify the slave status\n\n```SQL\nSTART SLAVE;\nSHOW SLAVE STATUS\\G\n```\n\n```SQL\n*************************** 1. row ***************************\n                Slave_IO_State: Waiting for master to send event\n                   Master_Host: alma9-c1\n...\n              Slave_IO_Running: Yes\n             Slave_SQL_Running: Yes\n...\n```\n\n### Test master-master replication\n\n#### Test on alma9-c1\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- create a testdatabase and a test-table\n\n```SQL\nCREATE DATABASE testdb;\n```\n\n#### test on alma9-c2\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- check the databases created in previous step\n\n```SQL\nSHOW DATABASES;\n```\n\n```SQL\n+--------------------+\n| Database           |\n+--------------------+\n| information_schema |\n| mysql              |\n| performance_schema |\n| testdb             |\n+--------------------+\n5 rows in set (0.000 sec)\n\nMariaDB [(none)]>\n```\n\n- delete database\n\n```SQL\nDROP DATABASE testdb;\n```\n\n#### verification on alma9-c1\n\n- back on alma9-c1 check the databases to make sure that \"testdb\" is not present anymore\n\n```SQL\nSHOW DATABASES;\n```\n\n```SQL\n+--------------------+\n| Database           |\n+--------------------+\n| information_schema |\n| mysql              |\n| performance_schema |\n+--------------------+\n4 rows in set (0.000 sec)\n\n```\n\n## Configure directory replication via Lsyncd\n\nThe following instructions are based on [an existing tutorial](https://docs.rackspace.com/docs/set-up-lsyncd-locally-and-over-ssh-to-sync-directories).\n\nTo accomplish a remote synchronization using Lsyncd, each node must have password-less SSH access to its peer. Further, it is recommended to use the root-user for synchronization to ensure that permissions, ownership, and group information of the synchronized objects will be preserved.\n\n### on both nodes to be executed as root-user\n\n- generate ssh keys\n\n```bash\nsudo ssh-keygen -t rsa -f /root/.ssh/id_lsyncd\n```\n\n- copy the newly created public key `/root/.ssh/id_lsyncd.pub` from each host to peer and add it to `/root/.ssh/authorized_keys` file\n\n- create the acme2certifier directory to be synchronized between the two hosts\n\n```bash\nsudo mkdir -p /opt/acme2certifier/volume\n```\n\n- install Lsyncd\n\n```bash\nsudo yum install -y lsyncd\n```\n\n### configuration on alma9-c1\n\n- test passwordless ssh access by logging in to alma9-c2\n\n```bash\nsudo ssh -i /root/.ssh/id_lsyncd root@alma9-c2\nexit\n```\n\n- create a configuration file `/etc/lsyncd.conf` with the following content\n\n```lua\nsettings {\n  logfile = \"/var/log/lsyncd/lsyncd.log\",\n  statusFile = \"/var/log/lsyncd/lsyncd.status\",\n  statusInterval = 20,\n  nodaemon   = false\n}\n\nsync {\n  default.rsyncssh,\n  source = \"/opt/acme2certifier/volume/\",\n  host = \"alma9-c2\",\n  targetdir = \"/opt/acme2certifier/volume/\",\n  rsync = {\n    rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n    compress = true,\n    owner = true,\n    group = true,\n    archive = true\n }\n}\n```\n\n- start Lsyncd and enable automatic startup\n\n```bash\nsudo systemctl restart lsyncd\nsudo systemctl enable lsyncd\n```\n\n### configuration on alma9-c2\n\n- test passwordless ssh access by logging in to alma9-c1\n\n```bash\nsudo ssh -i /root/.ssh/id_lsyncd root@alma9-c1\n```\n\n- create a configuration file `/etc/lsyncd.conf` with the following content\n\n```lua\nsettings {\n  logfile = \"/var/log/lsyncd/lsyncd.log\",\n  statusFile = \"/var/log/lsyncd/lsyncd.status\",\n  statusInterval = 20,\n  nodaemon   = false\n}\n\nsync {\n  default.rsyncssh,\n  source = \"/opt/acme2certifier/volume/\",\n  host = \"alma9-c1\",\n  targetdir = \"/opt/acme2certifier/volume/\",\n  rsync = {\n    rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n    compress = true,\n    owner = true,\n    group = true,\n    archive = true\n }\n}\n```\n\n- start Lsyncd and enable automatic startup\n\n```bash\nsudo systemctl restart lsyncd\nsudo systemctl enable lsyncd\n```\n\n### Test replication\n\n#### test replication on alma9-c1\n\n- create a file in `/opt/acme2certifier/volume` directory\n\n```bash\nsudo touch /opt/acme2certifier/volume/test.txt\n```\n\n#### test replication on alma9-c2\n\n- verify that the '/opt/acme2certifier/volume/test.txt' has been synchronized to alma9-c2 (please note that replication can take up to 20s)\n\n```bash\nsudo ls -la /opt/acme2certifier/volume\n```\n\n- delete the '/opt/acme2certifier/volume/test.txt'\n\n```bash\nsudo rm /opt/acme2certifier/volume/test.txt\n```\n\n#### Verify deletion on alma9-c1\n\n- back on alma9-c1 check `/opt/acme2certifier/volume` to make sure that \"test.txt\" has been deleted (please note that replication can take up to 20s)\n\n```bash\nsudo ls -la /opt/acme2certifier/volume\n```\n\nIn case of problem check the logfiles stored in `/var/log/lsyncd` for errors.\n\n## Install acme2certifier\n\n### on both nodes\n\n- Install django packages and mysqlclient\n\n```bash\nsudo yum install python3-mysqlclient python3-django3 python3-pyyaml -y\n```\n\n- Download the [latest rpm package](https://github.com/grindsa/acme2certifier/releases)\n- install the package locally and fix permissions\n\n```bash\nsudo yum localinstall -y ./acme2certifier_<version>-1.0.noarch.rpm\n sudo chown -R nginx /opt/acme2certifier/volume/\n```\n\n- Copy and activate nginx configuration file\n\n```bash\nsudo cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d\n```\n\n- Copy and activate nginx ssl configuration file (optional)\n\n```bash\nsudo cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d\n```\n\n- copy the django handler and the django directory structure\n\n```bash\nsudo cp /opt/acme2certifier/examples/db_handler/django_handler.py /opt/acme2certifier/acme_srv/db_handler.py\nsudo cp -r /opt/acme2certifier/examples/django/* /opt/acme2certifier/\n```\n\n- move the acme2certifier configuration file `acme_srv.cfg` into the mirrored diectory and create a symbolic link\n\n```bash\nsudo mv /opt/acme2certifier/acme_srv/acme_srv.cfg /opt/acme2certifier/volume/\nsudo ln -s /opt/acme2certifier/volume/acme_srv.cfg  /opt/acme2certifier/acme_srv/\n```\n\n- Enable and start the apache2 service\n\n```bash\nsudo systemctl enable acme2certifier\nsudo systemctl start acme2certifier\nsudo systemctl enable nginx\nsudo systemctl start nginx\n```\n\n### on alma9-c1\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- create a testdatabase and a test-table\n\n```SQL\nCREATE DATABASE acme2certifier CHARACTER SET UTF8;\nGRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY 'a2cpasswd';\nFLUSH PRIVILEGES;\n```\n\n- generate a new django secret-key and note it down\n\n```bash\npython3 /opt/acme2certifier/tools/django_secret_keygen.py\n+%*lei)yj9b841=2d5(u)a&7*uwi@l99$(*&ong@g*p1%q)g$e\n```\n\n- modify `/opt/acme2certifier/acme2certifier/settings.py` and\n  - insert the secret-key created in the previous step\n  - update the 'ALLOWED_HOSTS'- section with both ip-address and fqdn of the node\n  - configure a connection to mariadb as shown below\n\n```python\nSECRET_KEY = \"+%*lei)yj9b841=2d5(u)a&7*uwi@l99$(*&ong@g*p1%q)g$e\"\n...\nALLOWED_HOSTS = [\"192.168.14.136\", \"alma9-c1.bar.local\"]\n...\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"alma9-c1\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n```\n\n- Modify the [configuration file](acme_srv.md) `/opt/acme2certifier/volume/acme_srv.cfg`according to you needs. If your ca-handler needs runtime information (configuration files, keys, certificate-bundles etc.) to be shared between the nodes make sure that they get loaded from `/opt/acme2certifier/volume`. Below an example for the `[CAhandler]` section of the openssl-handler I use during my tests:\n\n```cfg\n[CAhandler]\nhandler_file: /opt/acme2certifier/examples/ca_handler/openssl_ca_handler.py\nca_cert_chain_list: [\"/opt/acme2certifier/volume/root-ca-cert.pem\"]\nissuing_ca_key: /opt/acme2certifier/volume/ca/sub-ca-key.pk8\nissuing_ca_key_passphrase_variable: OPENSSL_PASSPHRASE\nissuing_ca_cert: /opt/acme2certifier/volume/ca/sub-ca-cert.pem\nissuing_ca_crl: /opt/acme2certifier/volume/ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_validity_adjust: True\ncert_save_path: /opt/acme2certifier/volume/ca/certs\nsave_cert_as_hex: True\ncn_enforce: True\n```\n\n- create a django migration set, apply the migrations and load fixtures\n\n```bash\ncd /opt/acme2certifier\nsudo python3 manage.py makemigrations\nsudo python3 manage.py migrate\nsudo python3 manage.py loaddata acme_srv/fixture/status.yaml\n```\n\n- run the django_update script\n\n```bash\nsudo python3 /opt/acme2certifier/tools/django_update.py\n```\n\n- restart the acme2certifier service\n\n```bash\nsudo systemctl restart acme2certifier.service\n```\n\n- Test the server by accessing the directory ressource\n\n```bash\ncurl http://alma9-c1.bar.local/directory\n```\n\n```bash\n{\"newAccount\": \"http://alma9-c1.bar.local/acme_srv/newaccount\", \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\", \"keyChange\": \"http://alma9-c1.bar.local/acme_srv/key-change\", \"newNonce\": \"http://alma9-c1.bar.local/acme_srv/newnonce\", \"meta\": {\"home\": \"https://github.com/grindsa/acme2certifier\", \"author\": \"grindsa <grindelsack@gmail.com>\"}, \"newOrder\": \"http://alma9-c1.bar.local/acme_srv/neworders\", \"revokeCert\": \"http://alma9-c1.bar.local/acme_srv/revokecert\"}\n```\n\n### on alma9-c2\n\n- generate a new django secret and note it down\n\n```bash\npython3 /opt/acme2certifier/tools/django_secret_keygen.py\n5@@wlvvi!hb(6qc%*77j55@jt8ib4^f1o&+pz-^z*#v3e7u3o!\n```\n\n- modify `/opt/acme2certifier/acme2certifier/settings.py` and\n  - insert a secret key created in the previous step\n  - update the 'ALLOWED_HOSTS'- section with both IP-Adress and fqdn of the node\n  - configure a connection to mariadb as shown below\n\n```python\nSECRET_KEY = \"5@@wlvvi!hb(6qc%*77j55@jt8ib4^f1o&+pz-^z*#v3e7u3o!\"\n...\nALLOWED_HOSTS = [\"192.168.14.137\", \"alma9-c2.bar.local\"]\n...\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"alma9-c2\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n```\n\n- restart the acme2certifier service\n\n```bash\nsudo systemctl restart acme2certifier.service\n```\n\n- Test the server by accessing the directory ressource\n\n```bash\ncurl http://alma9-c2.bar.local/directory\n```\n\n```bash\n{\"newAccount\": \"http://alma9-c2.bar.local/acme_srv/newaccount\", \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\", \"keyChange\": \"http://alma9-c2.bar.local/acme_srv/key-change\", \"newNonce\": \"http://alma9-c2.bar.local/acme_srv/newnonce\", \"meta\": {\"home\": \"https://github.com/grindsa/acme2certifier\", \"author\": \"grindsa <grindelsack@gmail.com>\"}, \"newOrder\": \"http://alma9-c2.bar.local/acme_srv/neworders\", \"revokeCert\": \"http://alma9-c2.bar.local/acme_srv/revokecert\"}\n```\n\n## Test enrollment\n\n- try to enroll certificates from both nodes by using your favorite acme-client. I am using [lego](https://github.com/go-acme/lego) as this client supports multiple endpoints at once.\n\n- Example for enrollment from alma9-c1\n\n```bash\n docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://alma9-c1.bar.local -a --email \"lego@example.com\" -d lego01.bar.local --http run\n```\n\n- Example for enrollment from alma9-c2\n\n```bash\n docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://alma9-c2.bar.local -a --email \"lego@example.com\" -d lego01.bar.local --http run\n```\n"
  },
  {
    "path": "docs/a2c-ubuntu-loadbalancing.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title # How to build an acme2certifier cluster on Ubuntu 22.04 -->\n\n# How to build an acme2certifier cluster on Ubuntu 22.04\n\nThis tutorial describes the configuration of a two-node acme2certifier cluster running in active/active configuration. Although both nodes are active at the same time and provide proxy services via different IP addresses, database, configuration and and runtime objects will be replicated among the nodes.\n\nThis setup requires the switch to a different database engine as SQLite, which is the default a2c backend, is not designed to handle concurrent write access, which can happen in an active/active setup. Thus, [MariaDB](https://mariadb.org/) will be used. Configuration files and runtime objects will be replicated using [Lsyncd](https://github.com/lsyncd/lsyncd). The following diagram depicts the application stack to be used.\n\n![architecture](a2c-ubuntu-loadbalancing.png \"architecture\")\n\nThe guide is written for **Ubuntu 22.04**, however adapting to other Linux distributions should not be difficult. There is also a guide for [Alma Linux 9](a2c-alma-loadbalancing.md) available\n\n## Preparation\n\nTo set up the MariaDB Master-Master replication between multiple servers, you will need to ensure each system hostname is resolved to the correct IP address. I recommend setting up the FQDN in /etc/hosts on each server.\n\n```cfg\ncat /etc/hosts\n...\n192.168.14.132 ub2204-c1.bar.local ub2204-c1\n192.168.14.133 ub2204-c2.bar.local ub2204-c2\n```\n\nFurthermore, Apache2 should already be installed to create the directories to be replicated.\n\n```bash\nsudo apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3\n```\n\n## Installation and configuration of MariaDB\n\nThe following instructions are based on [an existing tutorial](https://www.howtoforge.com/how-to-setup-mariadb-master-master-replication-on-debian-11/).\n\n### Setting up ub2204-c1\n\n- install MariaDB-server\n\n```bash\nsudo apt install -y mariadb-server\n```\n\n- start MariaDB during startup\n\n```bash\nsudo systemctl is-enabled mariadb\nsudo systemctl status mariadb\n```\n\n- modify `/etc/mysql/mariadb.conf.d/50-server.cnf` change the ip-binding and add the following lines\n\n```cfg\n# listen on external address\nbind-address            = 192.168.14.132\n\nserver-id              = 1\nreport_host            = ub2204-c1\n\nlog_bin                = /var/log/mysql/mariadb-bin\nlog_bin_index          = /var/log/mysql/mariadb-bin.index\n\nrelay_log              = /var/log/mysql/relay-bin\nrelay_log_index        = /var/log/mysql/relay-bin.index\n\n# avoiding primary key collision\nlog-slave-updates\nauto_increment_increment=2\nauto_increment_offset=1\n```\n\n- restart MariaDB-server\n\n```bash\nsudo systemctl restart mariadb\n```\n\n- verify service binding\n\n```bash\nss -plnt\nState    Recv-Q   Send-Q        Local Address:Port       Peer Address:Port   Process\n...\nLISTEN   0        80           192.168.14.132:3306            0.0.0.0:*       users:((\"mariadbd\",pid=815,fd=43))\n...\n```\n\n- open the mysql command-line client\n\n```bash\nsudo mysql -u  root\n```\n\n- create the replication user\n\n```SQL\nCREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\nGRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\nFLUSH PRIVILEGES;\n```\n\n- Next, run the following query to check the current binary log and its exact position of it. In this example, the binary log file for the MariaDB server is \"mariadb-bin.000001\" with the position \"773\". These outputs will be used in the next stage for setting up the \"ub2204-c2\" server.\n\n```SQL\nSHOW MASTER STATUS;\n```\n\n```SQL\n+--------------------+----------+--------------+------------------+\n| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |\n+--------------------+----------+--------------+------------------+\n| mariadb-bin.000001 |      773 |              |                  |\n+--------------------+----------+--------------+------------------+\n1 row in set (0.000 sec)\n```\n\n### Setting up ub2204-c2\n\n- install MariaDB-server\n\n```bash\nsudo apt install -y mariadb-server\n```\n\n- start MariaDB during startup\n\n```bash\nsudo systemctl is-enabled mariadb\nsudo systemctl status mariadb\n```\n\n- modify `/etc/mysql/mariadb.conf.d/50-server.cnf` change the ip-binding and add the following lines\n\n```cfg\n# listen on external address\nbind-address            = 192.168.14.133\n\nserver-id              = 2\nreport_host            = ub2204-c2\n\nlog_bin                = /var/log/mysql/mariadb-bin\nlog_bin_index          = /var/log/mysql/mariadb-bin.index\n\nrelay_log              = /var/log/mysql/relay-bin\nrelay_log_index        = /var/log/mysql/relay-bin.index\n\n# avoiding primary key collision\nlog-slave-updates\nauto_increment_increment=2\nauto_increment_offset=2\n```\n\n- restart MariaDB-server\n\n```bash\nsudo systemctl restart mariadb\n```\n\n- verify service binding\n\n```bash\nss -plnt\n```\n\n```bash\nState    Recv-Q   Send-Q        Local Address:Port       Peer Address:Port   Process\n...\nLISTEN   0        80           192.168.14.133:3306            0.0.0.0:*       users:((\"mariadbd\",pid=841,fd=41))\n...\n```\n\n- open the mysql command-line client\n\n```bash\nsudo mysql -u  root\n```\n\n- create the replication user\n\n```SQL\nCREATE USER 'replusr'@'%' IDENTIFIED BY 'replpasswd';\nGRANT REPLICATION SLAVE ON *.* TO 'replusr'@'%';\nFLUSH PRIVILEGES;\n```\n\n- stop the slave and add information about the ub2204-c1 master node as well as the binlog file name (\"mariadb-bin.000001\") and position (\"773\") from ub2204-c1.\n\n```SQL\nSTOP SLAVE;\nCHANGE MASTER TO MASTER_HOST='ub2204-c1', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='mariadb-bin.000001', MASTER_LOG_POS=773;\n```\n\n- start the slave again and verify the slave status on the \"ub2204-c2\" server. You should get \"Slave_IO_Running: Yes\" and \"Slave_SQL_Running: Yes\",\n\n```SQL\nSTART SLAVE;\nSHOW SLAVE STATUS\\G\n```\n\n```SQL\n*************************** 1. row ***************************\n                Slave_IO_State: Waiting for master to send event\n                   Master_Host: ub2204-c1\n...\n              Slave_IO_Running: Yes\n             Slave_SQL_Running: Yes\n...\n```\n\n### Configure master-master replication on ub2204-c1\n\n- open the mysql command-line client and create the replication user\n\n```bash\nsudo mysql -u  root\n```\n\n- stop the slave and add information about the ub2204-c2 master node as well as the binlog filename and position.\n\n```SQL\nSTOP SLAVE;\nCHANGE MASTER TO MASTER_HOST='ub2204-c2', MASTER_USER='replusr', MASTER_PASSWORD='replpasswd', MASTER_LOG_FILE='mariadb-bin.000001', MASTER_LOG_POS=773;\n```\n\n- start the slave again and verify the slave status\n\n```SQL\nSTART SLAVE;\n```\n\n```SQL\nSHOW SLAVE STATUS\\G\n*************************** 1. row ***************************\n                Slave_IO_State: Waiting for master to send event\n                   Master_Host: ub2204-c1\n...\n              Slave_IO_Running: Yes\n             Slave_SQL_Running: Yes\n...\n```\n\n### Test master-master replication\n\n#### test replication on ub2204-c1\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- create a testdatabase and a test-table\n\n```SQL\nCREATE DATABASE testdb;\n```\n\n#### test replication on ub2204-c2\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- check the databases created in previous step\n\n```SQL\nSHOW DATABASES;\n+--------------------+\n| Database           |\n+--------------------+\n| information_schema |\n| mysql              |\n| performance_schema |\n| sys                |\n| testdb             |\n+--------------------+\n5 rows in set (0.000 sec)\n```\n\n```SQL\nMariaDB [(none)]>\n```\n\n- delete database\n\n```SQL\nDROP DATABASE testdb;\n```\n\n#### verify deletion on ub2204-c1\n\n- back on ub2204-c1 check the databases to make sure that \"testdb\" is not present anymore\n\n```SQL\nSHOW DATABASES;\n```\n\n```SQL\n+--------------------+\n| Database           |\n+--------------------+\n| information_schema |\n| mysql              |\n| performance_schema |\n| sys                |\n+--------------------+\n4 rows in set (0.000 sec)\n\n```\n\n## Configure directory replication via Lsyncd\n\nThe following instructions are based on [an existing tutorial](https://docs.rackspace.com/docs/set-up-lsyncd-locally-and-over-ssh-to-sync-directories).\n\nTo accomplish a remote synchronization using Lsyncd, each node must have password-less SSH access to its peer. Further, it is recommended to use the root-user for synchronization to ensure that permissions, ownership, and group information of the synchronized objects will be preserved.\n\n### on both nodes to be executed as root-user\n\n- generate ssh keys\n\n```bash\nsudo ssh-keygen -t rsa -f /root/.ssh/id_lsyncd\n```\n\n- copy the newly created public key `/root/.ssh/id_lsyncd.pub` from each host to peer and add it to `/root/.ssh/authorized_keys` file\n\n- create the acme2certifier directory to be synchronized between the two hosts\n\n```bash\nsudo mkdir -p /var/www/acme2certifier/volume\n```\n\n- install Lsyncd\n\n```bash\nsudo apt-get install -y lsyncd\n```\n\n- create the directory storing the configuration and log files\n\n```bash\nsudo mkdir /etc/lsyncd /var/log/lsyncd\n```\n\n### configuration on ub2204-c1\n\n- test passwordless ssh access by logging in to ub2204-c2\n\n```bash\nsudo ssh -i /root/.ssh/id_lsyncd root@ub2204-c2\nexit\n```\n\n- create a configuration file `/etc/lsyncd/lsyncd.conf.lua` with the following content\n\n```lua\nsettings {\n  logfile = \"/var/log/lsyncd/lsyncd.log\",\n  statusFile = \"/var/log/lsyncd/lsyncd.status\",\n  statusInterval = 20,\n  nodaemon   = false\n}\n\nsync {\n  default.rsyncssh,\n  source = \"/var/www/acme2certifier/volume/\",\n  host = \"ub2204-c2\",\n  targetdir = \"/var/www/acme2certifier/volume/\",\n  rsync = {\n    rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n    compress = true,\n    owner = true,\n    group = true,\n    archive = true\n }\n}\n```\n\n- start Lsyncd and enable automatic startup\n\n```bash\nsudo systemctl restart lsyncd\nsudo systemctl enable lsyncd\n```\n\n### configuration on ub2204-c2\n\n- test passwordless ssh access by logging in to ub2204-c1\n\n```bash\nsudo ssh -i /root/.ssh/id_lsyncd root@ub2204-c1\n```\n\n- create a configuration file `/etc/lsyncd/lsyncd.conf.lua` with the following content\n\n```lua\nsettings {\n  logfile = \"/var/log/lsyncd/lsyncd.log\",\n  statusFile = \"/var/log/lsyncd/lsyncd.status\",\n  statusInterval = 20,\n  nodaemon   = false\n}\n\nsync {\n  default.rsyncssh,\n  source = \"/var/www/acme2certifier/volume/\",\n  host = \"ub2204-c1\",\n  targetdir = \"/var/www/acme2certifier/volume/\",\n  rsync = {\n    rsh = \"/usr/bin/ssh -l root -i /root/.ssh/id_lsyncd -o StrictHostKeyChecking=no\",\n    compress = true,\n    owner = true,\n    group = true,\n    archive = true\n }\n}\n```\n\n- start Lsyncd and enable automatic startup\n\n```bash\nsudo systemctl restart lsyncd\nsudo systemctl enable lsyncd\n```\n\n### Test replication\n\n#### test repliasynchronizationtion on ub2204-c1\n\n- create a file in `/var/www/acme2certifier/volume` directory\n\n```bash\nsudo touch /var/www/acme2certifier/volume/test.txt\n```\n\n#### test synchronization on ub2204-c2\n\n- verify that the '/var/www/acme2certifier/volume/test.txt' has been synchronized to ub2204-c2 (please note that replication can take up to 20s)\n\n```bash\nsudo ls -la /var/www/acme2certifier/volume\n```\n\n- delete the '/var/www/acme2certifier/volume/test.txt'\n\n```bash\nsudo rm /var/www/acme2certifier/volume/test.txt\n```\n\n#### verify removal on ub2204-c1\n\n- back on ub2204-c1 check `/var/www/acme2certifier/volume` to make sure that \"test.txt\" has been deleted (please note that replication can take up to 20s)\n\n```bash\nsudo ls -la /var/www/acme2certifier/volume\n```\n\nIn case of problem check the logfiles stored in `/var/log/lsyncd` for errors.\n\n## Install acme2certifier\n\n### on both nodes\n\n- Download the [latest deb package](https://github.com/grindsa/acme2certifier/releases)\n- install the package locally\n\n```bash\nsudo apt-get install -y ./acme2certifier_<version>-1_all.deb\n```\n\n- Copy and activate apache2 configuration file\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-available/acme2certifier.conf\nsudo a2ensite acme2certifier\n```\n\n- Copy and activate apache2 ssl configuration file (optional)\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\nsudo a2ensite acme2certifier_ssl\n```\n\n- disable the default sites\n\n```bash\nsudo a2dissite 000-default.conf\nsudo a2dissite default-ssl\n```\n\n- copy the django handler and the django directory structure\n\n```bash\nsudo cp /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\nsudo cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\n```\n\n- move the acme2certifier configuration file `acme_srv.cfg` into the mirrored directory and create a symbolic link\n\n```bash\nsudo mv /var/www/acme2certifier/acme_srv/acme_srv.cfg /var/www/acme2certifier/volume/\nsudo ln -s /var/www/acme2certifier/volume/acme_srv.cfg  /var/www/acme2certifier/acme_srv/\n```\n\n- Enable and start the apache2 service\n\n```bash\nsudo systemctl enable apache2.service\nsudo systemctl start apache2.service\n```\n\n### setup on ub2204-c1\n\n- open the mysql commandline client\n\n```bash\nsudo mysql -u  root\n```\n\n- create a testdatabase and a test-table\n\n```SQL\nCREATE DATABASE acme2certifier CHARACTER SET UTF8;\nGRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY 'a2cpasswd';\nFLUSH PRIVILEGES;\n```\n\n- generate a new django secret-key and note it down\n\n```bash\npython3 /var/www/acme2certifier/tools/django_secret_keygen.py\n+%*lei)yj9b841=2d5(u)a&7*uwi@l99$(*&ong@g*p1%q)g$e\n```\n\n- modify `/var/www/acme2certifier/acme2certifier/settings.py` and\n  - insert the secret-key created in the previous step\n  - update the 'ALLOWED_HOSTS'- section with both ip-address and fqdn of the node\n  - configure a connection to mariadb as shown below\n\n```python\nSECRET_KEY = \"+%*lei)yj9b841=2d5(u)a&7*uwi@l99$(*&ong@g*p1%q)g$e\"\n...\nALLOWED_HOSTS = [\"192.168.14.132\", \"ub2204-c1.bar.local\"]\n...\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"ub2204-c1\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n```\n\n- Modify the [configuration file](acme_srv.md) `/var/www/acme2certifier/volume/acme_srv.cfg`according to you needs. If your ca-handler needs runtime information (configuration files, keys, certificate-bundles etc.) to be shared between the nodes make sure that they get loaded from `/var/www/acme2certifier/volume`. Below an example for the `[CAhandler]` section of the openssl-handler I use during my tests:\n\n```cfg\n[CAhandler]\nhandler_file: /var/www/acme2certifier/examples/ca_handler/openssl_ca_handler.py\nca_cert_chain_list: [\"/var/www/acme2certifier/volume/root-ca-cert.pem\"]\nissuing_ca_key: /var/www/acme2certifier/volume/ca/sub-ca-key.pk8\nissuing_ca_key_passphrase_variable: OPENSSL_PASSPHRASE\nissuing_ca_cert: /var/www/acme2certifier/volume/ca/sub-ca-cert.pem\nissuing_ca_crl: /var/www/acme2certifier/volume/ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_validity_adjust: True\ncert_save_path: /var/www/acme2certifier/volume/ca/certs\nsave_cert_as_hex: True\ncn_enforce: True\n```\n\n- create a django migration set, apply the migrations and load fixtures\n\n```bash\ncd /var/www/acme2certifier\nsudo python3 manage.py makemigrations\nsudo python3 manage.py migrate\nsudo python3 manage.py loaddata acme_srv/fixture/status.yaml\n```\n\n- run the django_update script\n\n```bash\nsudo python3 /var/www/acme2certifier/tools/django_update.py\n```\n\n- restart the apache2 service\n\n```bash\nsudo systemctl restart apache2.service\n```\n\n- Test the server by accessing the directory ressource\n\n```bash\ncurl http://ub2204-c1.bar.local/directory\n```\n\n```bash\n{\"newAccount\": \"http://ub2204-c1.bar.local/acme_srv/newaccount\", \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\", \"keyChange\": \"http://ub2204-c1.bar.local/acme_srv/key-change\", \"newNonce\": \"http://ub2204-c1.bar.local/acme_srv/newnonce\", \"meta\": {\"home\": \"https://github.com/grindsa/acme2certifier\", \"author\": \"grindsa <grindelsack@gmail.com>\"}, \"newOrder\": \"http://ub2204-c1.bar.local/acme_srv/neworders\", \"revokeCert\": \"http://ub2204-c1.bar.local/acme_srv/revokecert\"}\n```\n\n### setup on ub2204-c2\n\n- generate a new django secret and note it down\n\n```bash\npython3 /var/www/acme2certifier/tools/django_secret_keygen.py\n5@@wlvvi!hb(6qc%*77j55@jt8ib4^f1o&+pz-^z*#v3e7u3o!\n```\n\n- modify `/var/www/acme2certifier/acme2certifier/settings.py` and\n  - insert a secret key created in the previous step\n  - update the 'ALLOWED_HOSTS'- section with both IP-Adress and fqdn of the node\n  - configure a connection to mariadb as shown below\n\n```python\nSECRET_KEY = \"5@@wlvvi!hb(6qc%*77j55@jt8ib4^f1o&+pz-^z*#v3e7u3o!\"\n...\nALLOWED_HOSTS = [\"192.168.14.133\", \"ub2204-c2.bar.local\"]\n...\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"ub2204-c2\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n```\n\n- restart the apache2 service\n\n```bash\nsudo systemctl restart apache2.service\n```\n\n- Test the server by accessing the directory ressource\n\n```bash\ncurl http://ub2204-c2.bar.local/directory\n```\n\n```bash\n{\"newAccount\": \"http://ub2204-c2.bar.local/acme_srv/newaccount\", \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\", \"keyChange\": \"http://ub2204-c2.bar.local/acme_srv/key-change\", \"newNonce\": \"http://ub2204-c2.bar.local/acme_srv/newnonce\", \"meta\": {\"home\": \"https://github.com/grindsa/acme2certifier\", \"author\": \"grindsa <grindelsack@gmail.com>\"}, \"newOrder\": \"http://ub2204-c2.bar.local/acme_srv/neworders\", \"revokeCert\": \"http://ub2204-c2.bar.local/acme_srv/revokecert\"}\n```\n\n## Test enrollment\n\n- try to enroll certificates from both nodes by using your favorite acme-client. I am using [lego](https://github.com/go-acme/lego) as this client supports multiple endpoints at once.\n\n- Example for enrollment from ub2204-c1\n\n```bash\n docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://ub2204-c1.bar.local -a --email \"lego@example.com\" -d lego01.bar.local --http run\n```\n\n- Example for enrollment from ub2204-c2\n\n```bash\n docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://ub2204-c2.bar.local -a --email \"lego@example.com\" -d lego01.bar.local --http run\n```\n"
  },
  {
    "path": "docs/acme-clients.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Example commands for acme clients -->\n\n# acme.sh\n\n## register account\n\n```bash\nacme.sh --server http://<server address> --register-account --accountemail <email address> --debug 2 --output-insecure\n```\n\n## deactivate account\n\n```bash\nacme.sh --server http://<server address> --deactivate-account --debug 2 --output-insecure\n```\n\n## cert enrollment\n\n```bash\nacme.sh --server http://<server address>  --issue -d acme-1.example.com -d acme-2.example.com --standalone --debug 2 --output-insecure --force\n```\n\n## cert revocation\n\n```bash\nacme.sh --server http://<server address> --revoke -d acme-1.example.com -d acme-2.example.com --debug 2 --output-insecure\n```\n\n# Certbot\n\n## account registration\n\n```bash\ncertbot register --agree-tos -m <email address>  --server http://<server address> --no-eff-email\n```\n\n## account deletion\n\n```bash\nrm -rf /etc/letsencrypt/accounts/*\n```\n\n## certificate enrollment\n\n```bash\ncertbot certonly --server http://<server address> --standalone --preferred-challenges http -d certbot-1.example.com -d certbot-2.example.com --cert-name certbot-test\n```\n\n## certificate revocation\n\n```bash\ncertbot revoke --server http://<server address> --cert-name certbot-test\n```\n\nIMPORTANT: by default a CSR generated by certbot does not contain any subject name. Such CSR will be refused by enterprise CA servers. For mitigation you need to create a CA policy setting a subject name.\n[Example CA policy for Insta Certifier](certifier.md)\n\n# lego\n\n## account registration and cert enrollment\n\n```bash\nlego -s http://<server address> -a --email <email address> -d lego-1.bar.local -d lego-2.bar.local --http run\n```\n\n## revoke a certificate\n\n```bash\nlego -s http://<server address> -a --email <email address> -d lego-1.bar.local revoke\n```\n\n# acmeshell\n\n## start the shell\n\n```bash\nacmeshell -directory http://<server address> -postAsGet=true\n```\n\n## create a new account\n\n```bash\n> newAccount -contacts=grindsa@foo.bar,\n```\n\n## create a new order\n\n```bash\n> newOrder -identifiers=foo.bar\n```\n\n## get status of the order\n\n```bash\n> getOrder -order 0\n```\n\n## get authorization details for order\n\n```bash\n> getAuthz -order=0 -identifier=foo.bar\n```\n\n## get http challenges\n\n```bash\n> getChall -order=0 -identifier=foo.bar -type=http-01\n```\n\n## solve http challenge of order's auth\n\n```bash\n> solve -order=0 -identifier=foo.bar -challengeType=http-01\n```\n\n## poll orderstatus (still pending)\n\n```bash\n> poll -order=0\n```\n\n## finalize order\n\n```bash\n> finalize -order=0\n```\n\n## poll order to check status\n\n```bash\n> poll -order=0 -status=valid\n```\n\n## get certificate\n\n```bash\n> getCert -order=0\n```\n"
  },
  {
    "path": "docs/acme_ca.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title ACME CA handler -->\n\n# ACME CA Handler\n\nUsing `acme2certifier` to proxy requests towards ACME endpoints sounds like a silly idea?\n\nNot at all... Just think about the following use cases:\n\n- You would like to use certificates from Let's Encrypt or ZeroSSL for servers in your internal network without exposing your systems to the internet.\n- You are using a commercial ACME server (Entrust, Netnumber, Sectigo) with pre-authenticated domains. You would like to provide access across an organization without sharing the commercial endpoint's private key.\n\nEspecially the first use case is an interesting one. However, it requires specific your network and DNS configuration as CA used for certificate issuance needs to validate the acme-challenges by using either HTTP (default) or DNS.\n\nIf you are planning to use DNS Challenge validation please note:\n\nYour `acme2certifier` server must be able to reach the CA and needs to have access to your DNS server to provision the [key-authorization record](https://datatracker.ietf.org/doc/html/rfc8555/#section-8.4).\nI’ve decided to implement a script-based mechanism for DNS challenge provisioning, providing flexibility in how DNS challenges are handled. This implementation will be compatible with [acme.sh dns plugins](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) allowing a reuse of the acme-dns plugin library.\nTherefore, you’ll need to download acme.sh (it won’t be executed directly) as well as the DNS API plugin for your DNS provider.\n\nThe configuration will be managed through the acme_srv.cfg file. The below example configuration refers to the [CloudFlare DNS plugin](https://github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf)\n\n```cfg\n[CAhandler]\nhandler_file: /opt/acme2certifier/examples/ca_handler/acme_ca_handler.py\nacme_sh_script: /opt/acme2certifier/volume/acme.sh\nacme_sh_shell: /bin/bash\n# setting the dns_update_script parameter will force a2c to use dns-challenges for validation only\ndns_update_script: /opt/acme2certifier/volume/dns_cf.sh\ndns_update_script_variables: {\"CF_Token\": \"your_cf_token\", \"CF_Zone_ID\": \"your_cf_zone-id\"}\ndns_validation_timeout: 10\n```\n\nIn case you are planning to use HTTP Challenge validaton:\n\n- Your `acme2certifier` server must be able to reach the CA.\n- Your `acme2certifier` server needs to be accessible from the internet.\n- The DNS domain you are using internally must be an official one, and you need to have ownership of it.\n- You need to provide different sets of DNS information depending on the source address of the DNS request. Your internal clients and servers (including `acme2certifier`) need to resolve the addresses of your internal network, while external systems (especially the CA servers) need to get the (external) address of `acme2certifier` when querying the same namespace. In my test environment, I fulfill this requirement by:\n  - Separating external and internal DNS onto different systems.\n  - Creating a wildcard record on my external DNS (`*.foo.com`) pointing to `acme2certifier`.\n  - Using the internal DNS on my `acme2certifier` instance.\n  - Optional: Using the external DNS server as a forwarder for the internal DNS server.\n\nAs of today, the `acme_ca_handler` supports the following operations:\n\n- Account registration\n- HTTP or DNS challenge validation\n- Certificate enrollment\n- Certificate revocation\n\n## Supported CAs\n\n- [Let's Encrypt](https://letsencrypt.org/)\n- [BuyPass](https://www.buypass.com/)\n- [ZeroSSL](https://zerossl.com/)\n\n## Prerequisites\n\nAgain, it is important to mention that the handler validates challenges over HTTP. Thus, it must be ensured that the HTTP requests from the CA server reach `acme2certifier`.\n\n## Configuration\n\nThe handler must be configured via `acme_srv`.\n\n| Option                          | Description                                                                                                                                                    | Mandatory | Default      |\n| :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------: | :----------- |\n| handler_file                    | Path to CA handler file                                                                                                                                        |    Yes    | None         |\n| account_path                    | Path to account resource on CA server                                                                                                                          |    No     | `/acme/acct` |\n| acme_url                        | URL of the ACME endpoint                                                                                                                                       |    Yes    | None         |\n| acme_account                    | ACME account name. If not specified, `acme2certifier` will try to look up the account name based on the key file                                               |    No     | None         |\n| acme_keyfile                    | Path to private key (JSON format). If specified in config but not existing on the file system, `acme2certifier` will generate a new key and try to register it |    No     | None         |\n| acme_keypath                    | Path to private key directory. If specified in config, `acme2certifier` stores new keys in this directory                                                      |    No     | None         |\n| acme_account_email              | Email address used to register a new account                                                                                                                   |    No     | None         |\n| acme_sh_script                  | path to the acme_sh.sh script to be used for DNS challenge provisioning                                                                                        |    No     | None         |\n| acme_sh_shell                   | shell to be used to execute acme_sh                                                                                                                            |    No     | /bin/bash    |\n| profiles_sync                   | Enable periodic synchronization of profiles information from ACME server to be shown as meta-information in Directory ressource                                |    No     | False        |\n| profiles_sync_interval          | Interval in seconds for profile synchronization when enabled.                                                                                                  |    No     | 604800       |\n| allowed_domainlist              | List of domain names allowed for enrollment in JSON format, e.g., `[\"bar.local\", \"bar.foo.local\"]`                                                             |    No     | `[]`         |\n| directory_path                  | Path to directory resource on CA server                                                                                                                        |    No     | `/directory` |\n| dns_update_script               | Path to the script script to provision DNS records for DNS challenge validation. Setting the dns_update_script will force a2c to trigger dns-challenge validation.   | No  | None         |\n| dns_update_script_variables.    | Environment variables for the DNS update script in JSON format, e.g. `{\"CF_Token\": \"your_cf_token\", \"CF_Zone_ID\": \"your_cf_zone-id\"}`                          |    No     | None         |\n| dns_validation_timeout          | sleep timer after dns provisioning                                                                                                                             |    No     | 10           |\n| enrollment_config_log           | Log enrollment parameters                                                                                                                                      |    No     | `False`      |\n| enrollment_config_log_skip_list | List of enrollment parameters not to be logged in JSON format, e.g., `[\"parameter1\", \"parameter2\"]`                                                            |    No     | `[]`         |\n| ssl_verify                      | Verify certificates on SSL connections                                                                                                                         |    No     | `True`       |\n| renewalinfo_lookup              | Enable or disable renewalinfo endpoint lookup on ACME server to obtain renewal window                                                                          |    No     | False        |\n\nModify the server configuration (`acme_srv/acme_srv.cfg`) and add at least the following parameters:\n\n```cfg\n[CAhandler]\n# CA specific options\nhandler_file: examples/ca_handler/acme_ca_handler.py\nacme_url: https://some.acme/endpoint\nacme_keyfile: /path/to/privkey.json\n```\n\n## Example Configurations\n\n### Let's Encrypt\n\n```cfg\n[CAhandler]\nacme_keyfile: acme_srv/acme/le_staging_private_key.json\nacme_url: https://acme-staging-v02.api.letsencrypt.org\nacme_account_email: email@example.com\n```\n\nFor production:\n\n```cfg\nacme_url: https://acme-v02.api.letsencrypt.org\nacme_keyfile: /var/www/acme2certifier/volume/acme/le_private_key.json\n```\n\n### BuyPass\n\n```cfg\nacme_keyfile: acme_srv/acme/buypass_test_private_key.json\nacme_url: https://api.test4.buypass.no/acme\nacme_account_email: email@example.com\n```\n\nFor production:\n\n```cfg\nacme_keyfile: acme_srv/acme/buypass_prod_private_key.json\nacme_url: https://api.buypass.com/acme\nacme_account_email: email@example.com\n```\n\n### ZeroSSL\n\n```cfg\nacme_keyfile: acme_srv/acme/zerossl.json\nacme_url: https://acme.zerossl.com/v2/DV90\nacme_account_email: email@example.com\naccount_path: /account/\n```\n\n### Smallstep CA\n\n```cfg\nacme_keyfile: acme_srv/acme/smallstep.json\nacme_url: https://<fqdn>/acme/myacme\nacme_account_email: email@example.com\naccount_path: /\nssl_verify: False\n```\n\n## Example Key File\n\n```json\n{\n  \"kty\": \"RSA\",\n  \"n\": \"...\",\n  \"e\": \"AQAB\",\n  \"d\": \"...\"\n}\n```\n\n## Passing a Profile ID\n\nTo allow an ACME client to specify an ACME backend address, enable this feature in `acme_srv.cfg`:\n\n```cfg\n[Order]\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nExample for `acme.sh`:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent acme_url=<acme-server url> --debug 3 --output-insecure\n```\n\nExample for `lego`:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent acme_url=<acme-server url> -d <fqdn> --http run\n```\n\n# EAB Profiling\n\nTo enable EAB profiling:\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nacme_key_path: <path>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nExample key-file:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"...\",\n    \"cahandler\": {\n      \"acme_url\": [\"https://acme-staging-v02.api.letsencrypt.org\"],\n      \"allowed_domainlist\": [\"www.example.com\"]\n    }\n  }\n}\n```\n"
  },
  {
    "path": "docs/acme_profiling.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: Support for ACME profiling -->\n\n# Support for ACME Profiles Extension\n\nThe [Automated Certificate Management Environment (ACME) Profiles Extension draft](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/) proposes a method for ACME servers to offer multiple certificate profiles, allowing clients to select certificates that align with specific requirements, such as validity periods or key usage constraints. This enhancement aims to provide greater flexibility and security by enabling clients to choose from predefined profiles advertised by the server, thereby reducing reliance on custom Certificate Signing Requests (CSRs).\n\nacme2certifier supports acme profiling starting from version v0.38.\n\nACME profiling must be must be specified in `acme_srv.cfg`:\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}\n```\n\nBelow an example for lego submitting a profile \"profile2\":\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile profile2\n```\n\nacme2certifier will check a submitted profile-name against the list of advertised profiles. If a client submits an order for an unknown profile the order the order will get refused with an \"invalidProfile\" error. acme2certifier can be configured to skip this check and accept any profile name as long as profiling gets enabled in the config.\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}\nprofiles_check_disable: True\n```\n\nDepending on the CA-handler the profile value replaces a certain value in the CA-handler configuration. The below table provides an overview about the individual paramters:\n\n| CA-handler                                                                      | configuration parameter |\n| ------------------------------------------------------------------------------- | ----------------------- |\n| [ACME Handler](acme_ca.md)                                                      | profile                 |\n| [DigiCert® CertCentral](digicert.md)                                            | cert_type               |\n| [EJBCA](ejbca.md)                                                               | cert_profile_name       |\n| [Insta ActiveCMS](asa.md)                                                       | profile_name            |\n| [Microsoft Certificate Enrollment Web Services](mscertsrv.md)                   | template                |\n| [Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE)](mswcce.md) | template                |\n| [NetGuard Certificate Manager/Insta Certifier](certifier.md)                    | profile_id              |\n| [OpenXPKI](openxpki.md)                                                         | cert_profile_name       |\n| [XCA](xca.md)                                                                   | template_name           |\n\nThe profile value will be added to the `profile` column of the orders table. A CA handler can obtail the value using the `eab_profile_header_info_check()` function from `helper.py`.\n\n```python\nfrom acme_srv.helper import (\n    eab_profile_header_info_check,\n    ...\n)  # pylint: disable=e0401\n\nclass CAHandler(object):\n    ...\n    def __init__(self, _debug: bool = False, logger: object = None):\n        template = None\n\n    def enroll(self, csr):\n        \"\"\"Enroll certificate\"\"\"\n        self.logger.debug('CAHandler.enroll()')\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_identifier = None\n\n        # Lookup HTTP header information from request\n        error = eab_profile_header_info_check(\n            self.logger, self, csr, \"template\"\n        )\n        if not error:\n            self.logger.info('Profile: {0}'.format(self.template))\n            # Perform additional processing with the profile information...\n        ...\n        self.logger.debug('Certificate.enroll() ended')\n\n        return (error, cert_bundle, cert_raw, poll_identifier)\n```\n"
  },
  {
    "path": "docs/acme_srv.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Configuration options for acme2certifier -->\n\n# acme_srv.cfg\n\n## configuration options for acme2certifier\n\n| Section         | Option                                | Description                                                                                                                                                                                                           | Values                                                                                                                  | default                                     |\n| :-------------- | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ |\n| `DEFAULT`       | `debug`                               | Debug mode                                                                                                                                                                                                            | True/False                                                                                                              |\nFalse                                       |\n| `DEFAULT`       | `async_mode`                          | Enable [asynchronous Mode](async_mode.md)                                                                                                                                                                             | True/False                                                                                                              | False                                       |\n| `DEFAULT`       | `proxy_server_list`                   | [Proxy-server configuration](proxy_support.md)                                                                                                                                                                        | {\"bar.local$\": \"http​://10.0.0.1:3128\", \"foo.local$\": \"socks5://10.0.0.1:1080\"}                                          | None                                        |\n| `Account`       | `contact_check_disable`               | do not require to send contact information                                                                                                                                                                            | True/False                                                                                                              | False                                       |\n| `Account`       | `ecc_only`                            | mandates the usage of ECC for account key generation                                                                                                                                                                  | True/False                                                                                                              | False                                       |\n| `Account`       | `inner_header_nonce_allow`            | allow nonce header on inner JWS during key-rollover                                                                                                                                                                   | True/False                                                                                                              | False                                       |\n| `Account`       | `tos_check_disable`                   | turn off \"Terms of Service\" acceptance check                                                                                                                                                                          | True/False                                                                                                              | False                                       |\n| `Authorization` | `expiry_check_disable`                | Disable authorization expiration                                                                                                                                                                                      | True/False                                                                                                              | False                                       |\n| `Authorization` | `prevalidated_domainlist`             | List of [pre-validated identfiers](prevalidated_domainlist.md)                                                                                                                                                        | \\[\"host-01.bar.local\", \"\\*.example.local\"\\]                                                                                | None                                        |\n| `Authorization` | `validity`                            | Authorization validity in seconds                                                                                                                                                                                     | Integer                                                                                                                 | 86400                                       |\n| `CAhandler`     | `handler_file`                        | path and name of ca_handler file to be loaded. If not specified `acme_srv/ca_handler.py` will be loaded                                                                                                               | examples/ca_handler/openssl_handler.py                                                                                  | `acme_srv/ca_handler.py`                    |\n| `Certificate`   | `cert_reusage_timeframe`              | in case a csr will be resend within this timeframe (in seconds) the certificate already stored in the database will be returned and no enrollment will be triggered                                                   | Integer                                                                                                                 | 0 (disabled)                                |\n| `Certificate`   | `enrollment_timeout`                  | timeout in seconds for asynchronous ca_handler threat                                                                                                                                                                 | Integer                                                                                                                 | 5                                           |\n| `Certificate`   | `cert_operations_log`                 | log certificate issuance and revocation operations                                                                                                                                                                    | True/False/json                                                                                                         | False                                       |\n| `Certificate`   | `revocation_reason_check_disable`     | disable the check of revocation reason                                                                                                                                                                                | True/False                                                                                                              | False                                       |\n| `Challenge`     | `challenge_validation_disable`        | disable challenge validation via http or dns. THIS IS A SEVERE SECURITY ISSUE! Please enable for testing/debugging purposes only.                                                                                     | True/False                                                                                                              | False                                       |\n| `Challenge`     | `challenge_validation_timeout`        | Timeout in seconds for challenge validation                                                                                                                                                                           | Integer                                                                                                                 | 10                                          |\n| `Challenge`     | `forward_address_check`               | Only in combination with `challenge_validation_disable` parameter. a2c checks if the acme-client ip is registered for the fqdns being part of the order request                                                       | True/False                                                                                                              | False                                       |\n| `Challenge`     | `reverse_address_check`               | Only in combination with `challenge_validation_disable` parameter. a2c checks if dns-name part of the order request is a PTR record for the client-ip sending the request                                             | True/False                                                                                                              | False                                       |\n| `Challenge`     | `dns_server_list`                     | Use own dns servers for name resolution during challenge verification                                                                                                                                                 | \\[\"ip1\", \"ip2\"\\]                                                                                                        | \\[\\]                                        |\n| `Challenge`     | `dns_validation_pause_timer`          | pause interval in seconds after failed validation of a dns challenge                                                                                                                                                  | 10                                                                                                                      | 0.5                                         |\n| `Challenge`     | `sectigo_sim`                         | provide `sectigo-email-01` challenges - Only for development and testing!                                                                                                                                             | True/False                                                                                                              | False                                       |\n| `DBhandler`     | `dbfile`                              | path and name of database file. If not specified `acme_srv/acme_srv.db` will be used. Parameter is only available for a wsgi handler and will be ignored if django handler is getting used                            | 'acme/database.db'                                                                                                      | `acme_srv/acme_srv.db`                      |\n| `Directory`     | `caaidentities`                       | ACME server hostname\\[s\\] for CAA record validation as defined in [RFC6844](https://www.rfc-editor.org/rfc/rfc6844)                                                                                                   | 'string'                                                                                                                | None                                        |\n| `Directory`     | `db_check`                            | check database connection compare schemes and report as OK/NOK in meta information                                                                                                                                    | True/False                                                                                                              | False                                       |\n| `Directory`     | `home`                                | homepage string to be shown when fetching the directory ressource                                                                                                                                                     | 'string'                                                                                                                | 'https://github.com/grindsa/acme2certifier' |\n| `Directory`     | `supress_product_information`         | Do not show product name, author and version when fetching the directory resource                                                                                                                                     | True/False                                                                                                              | False                                       |\n| `Directory`     | `supress_version`                     | Do not show version information when fetching the directory resource                                                                                                                                                  | True/False                                                                                                              | False                                       |\n| `Directory`     | `tos_url`                             | Terms of Service URL                                                                                                                                                                                                  | URL                                                                                                                     | None                                        |\n| `Directory`     | `url_prefix`                          | url prefix for acme2certifier resources                                                                                                                                                                               | '/foo'                                                                                                                  | None                                        |\n| `EABhandler`    | `eab_handler_file`                    | EAB handler file                                                                                                                                                                                                      | path/file                                                                                                               | None                                        |\n| `EABhandler`    | `key_file`                            | EAB credential file                                                                                                                                                                                                   | path/file                                                                                                               | None                                        |\n| `EABhandler`    | `eabkid_check_disable`                | validate kid during every transaction                                                                                                                                                                                 | True/False                                                                                                              | False                                       |\n| `EABhandler`    | `invalid_eabkid_deactivate`           | deactivate invalid eab-kids                                                                                                                                                                                           | True/False                                                                                                              | False                                       |\n| `Helper`        | `log_format`                          | Format of logging information                                                                                                                                                                                         | check the 'LogRecord attributes' Section of the [python logging module](https://docs.python.org/3/library/logging.html) | `%(message)s`                               |\n| `Hooks`         | `hooks_file`                          | path and name of hooks (for pre- and post-enrollment hooks) file to be loaded                                                                                                                                         | None                                                                                                                    |                                             |\n| `Hooks`         | `ignore_pre_hook_failure`             | True/False                                                                                                                                                                                                            | False                                                                                                                   |                                             |\n| `Hooks`         | `ignore_post_hook_failure`            | True/False                                                                                                                                                                                                            | True                                                                                                                    |                                             |\n| `Hooks`         | `ignore_success_hook_failure`         | True/False                                                                                                                                                                                                            | False                                                                                                                   |                                             |\n| `Message`       | `signature_check_disable`             | disable signature check of incoming JWS messages. THIS IS A SEVERE SECURITY ISSUE bypassing security checks and allowing message manipulations during transit. Please enable for testing/debugging purposes only.     | True/False                                                                                                              | False                                       |\n| `Nonce`         | `nonce_check_disable`                 | disable nonce check. THIS IS A SECURITY ISSUE as it exposes the API for replay attacks! Should be enabled for testing/debugging purposes only.                                                                        | True/False                                                                                                              | False                                       |\n| `Order`         | `expiry_check_disable`                | Disable order expiration                                                                                                                                                                                              | True/False                                                                                                              | False                                       |\n| `Order`         | `header_info_list`                    | HTTP header fields to be passed to ca handler                                                                                                                                                                         | \\[\"HTTP_USER_AGENT\", \"FOO_BAR\"\\]                                                                                        | \\[\\]                                        |\n| `Order`         | `retry_after_timeout`                 | Retry-After value to be send to client in case a certificate enrollment request gets pending on CA server                                                                                                             | Integer                                                                                                                 | 120                                         |\n| `Order`         | `identifier_limit`                    | Maximum number of identifiers submitted in a single order request which translate later into SANs per certificate                                                                                                     | Integer                                                                                                                 | 20                                          |\n| `Order`         | `idempotent_finalize`                 | Allow Non-RFC compliant order polling via finalization request                                                                                                                                                        | True/False                                                                                                              | False                                       |\n| `Order`         | [`tnauthlist_support`](tnauthlist.md) | accept [TNAuthList identifiers](https://tools.ietf.org/html/draft-ietf-acme-authority-token-tnauthlist-03) and challenges containing [tkauth-01 type](https://tools.ietf.org/html/draft-ietf-acme-authority-token-03) | True/False                                                                                                              | False                                       |\n| `Order`         | `validity`                            | Order validity in seconds                                                                                                                                                                                             | Integer                                                                                                                 | 86400                                       |\n| `Order`         | `profiles`                            | specifies [acme-profiles](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/) to be offered by the server                                                                                                    | {\"profile1\": \"url1\", \"profile2\": \"url2\"}                                                                                | {}                                          |\n| `Order`         | `profiles_check_disable`              | Disables validation of the client-submitted profile against the profiles advertised by the server                                                                                                                     | True/False                                                                                                              | False                                       |\n| `Renewalinfo`   | `renewalthreshold_pctg`               | Defines the percentage of certificate lifetime after which renewal is allowed or recommended                                                                                                                          | Integer                                                                                                                 | 85                                          |\n| `Renewalinfo`   | `retry_after_timeout`                 | Number of seconds a client should wait before retrying a pending certificate renewal                                                                                                                                  | Integer                                                                                                                 | 600                                         |\n| `Renewalinfo`   | `renewal_force`                       | Forces certificate renewal regardless of the usual renewal threshold or timing conditions                                                                                                                             | True/False                                                                                                              | False                                       |\n\nThe options for the `CAhandler` section depend on the CA handler.\n\nFurther options for the `Hooks` section depend on the concrete hooks class.\n\nInstructions for [Insta Certifier](certifier.md)\n\nInstructions for [NetGuard Certificate Lifecycle Manager](nclm.md)\n\nInstructions for [Microsoft Certification Authority Web Enrollment Service](mscertsrv.md)\n\nInstructions for the [generic EST handler](est.md)\n\nInstructions for the [generic CMPv2 handler](cmp.md)\n\nInstructions for [XCA handler](xca.md)\n\nInstructions for [Openssl based CA handler](openssl.md)\n"
  },
  {
    "path": "docs/architecture/account-architecture.md",
    "content": "# Account Architecture Documentation\n\n## Overview\n\nThis document describes the refactored account architecture and provides guidance for understanding and extending the account management system in acme2certifier.\n\n## Architecture Summary\n\nThe account subsystem implements a modular, extensible architecture using established design patterns to handle ACME account lifecycle management:\n\n### Design Patterns Implemented\n\n- **Repository Pattern**: Clean separation of data access logic from business logic\n- **Business Logic Layer**: Domain-specific account operations and business rules\n- **Configuration Pattern**: Centralized configuration management with validation\n- **Context Manager Pattern**: Resource management and initialization\n- **Exception Hierarchy**: Structured error handling with specific error types\n- **Data Transfer Objects**: Structured configuration and data containers\n\n### Component Structure\n\n```text\n┌─────────────────────────────────────────────────────────────┐\n│                    Account Class                            │\n│               (ACME Protocol Handler)                       │\n└─────────────────────────────┬───────────────────────────────┘\n                              │\n        ┌───────────────┬───────────────┬───────────────┐\n        │               │               │               │\n        ▼               ▼               ▼               ▼\n┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐\n│AccountRepo  │ │AccountLogic │ │AccountConfig│ │AccountDTO   │\n│(DB access)  │ │(Business    │ │(Config      │ │(Data        │\n│             │ │ Logic)      │ │ Object)     │ │ Transfer)   │\n└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘\n        │               │               │               │\n        ▼               ▼               ▼               ▼\n    DBstore        Validation      Helpers        Error/Logger\n```\n\n## Core Components\n\n### 1. Account Class (`/acme_srv/account.py`)\n\nThe `Account` class is the main entry point for account operations and ACME protocol handling. It provides methods for:\n\n- Account creation and registration\n- Account status management\n- Key rollover and update\n- External Account Binding (EAB) support\n- Error handling and logging\n\n### 2. AccountRepository\n\nHandles all database-related operations for accounts, abstracting the underlying database handler. Key responsibilities:\n\n- Fetching and storing account data\n- Managing account status and keys\n- Logging and error handling for database access\n\n### 3. AccountBusinessLogic\n\nImplements domain-specific business rules for account management, including:\n\n- Validation of account requests\n- Key management and rollover logic\n- EAB validation and processing\n\n### 4. AccountConfig\n\nCentralizes configuration management for account operations, supporting:\n\n- Feature toggles (e.g., EAB, key rollover)\n- Validation rules\n- Integration with external systems\n\n## Configuration Loading\n\nThe account subsystem loads its configuration from external sources using helper functions. It parses:\n\n- Feature flags\n- Validation rules\n- EAB parameters\n- Logging and error handling settings\n\n## Error Handling\n\nA structured exception hierarchy is used to provide robust error handling, including:\n\n- Specific error types for account operations\n- Logging and reporting mechanisms\n- Integration with ACME protocol error responses\n\n______________________________________________________________________\n"
  },
  {
    "path": "docs/architecture/authorization-architecture.md",
    "content": "# Authorization Architecture Documentation\n\n## Overview\n\nThis document describes the refactored authorization architecture and provides guidance for understanding and extending the authorization system in acme2certifier.\n\n## Architecture Summary\n\nThe refactored authorization system implements a clean, modular architecture using established design patterns to handle ACME authorization lifecycle management:\n\n### Design Patterns Implemented\n\n- **Repository Pattern**: Clean separation of data access logic from business logic\n- **Business Logic Layer**: Domain-specific authorization operations and business rules\n- **Configuration Pattern**: Centralized configuration management with validation\n- **Manager Pattern**: Specialized managers for challenge set operations\n- **Data Transfer Objects**: Structured data containers with validation\n- **Context Manager Pattern**: Resource management and initialization\n- **Exception Hierarchy**: Structured error handling with specific error types\n\n### Component Structure\n\n```text\n┌─────────────────────────────────────────────────────────────┐\n│                  Authorization Class                        │\n│               (ACME Protocol Handler)                       │\n└──────────────────────┬──────────────────────────────────────┘\n                       │\n    ┌──────────────────┼──────────────────┐\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐\n│Authorization    │ │Authorization    │ │ChallengeSet     │\n│Repository       │ │BusinessLogic    │ │Manager          │\n│                 │ │                 │ │                 │\n└─────────────────┘ └─────────────────┘ └─────────────────┘\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐\n│  DBstore        │ │AuthorizationData│ │Challenge        │\n│  Database Ops   │ │AuthorizationConf│ │Integration      │\n└─────────────────┘ └─────────────────┘ └─────────────────┘\n    │\n    ▼\n┌─────────────────┐\n│  SQLite/MySQL   │\n│  Database       │\n└─────────────────┘\n```\n\n## Core Components\n\n### 1. Data Models (`AuthorizationConfig` & `AuthorizationData`)\n\nData structures that define authorization configuration and runtime data:\n\n#### `AuthorizationConfig`\n\n```python\n@dataclass\nclass AuthorizationConfig:\n    \"\"\"Configuration for Authorization operations\"\"\"\n\n    validity: int = 86400  # Default 24 hours\n    expiry_check_disable: bool = False\n    authz_path: str = \"/acme/authz/\"\n```\n\n**Responsibilities:**\n\n- Store authorization validity period configuration\n- Control expiry checking behavior\n- Define URL path structure\n\n#### `AuthorizationData`\n\n```python\n@dataclass\nclass AuthorizationData:\n    \"\"\"Authorization data structure\"\"\"\n\n    name: str\n    status: str\n    expires: int\n    token: str\n    identifier: Optional[Dict[str, str]] = None\n    challenges: Optional[List[Dict[str, str]]] = None\n    wildcard: bool = False\n```\n\n**Responsibilities:**\n\n- Structured representation of authorization data\n- ACME-compliant serialization via `to_dict()` method\n- Type safety and validation\n\n### 2. Repository Layer (`AuthorizationRepository`)\n\nHandles all database operations with clean abstraction:\n\n```python\nclass AuthorizationRepository:\n    \"\"\"Repository class for authorization database operations\"\"\"\n```\n\n**Key Methods:**\n\n- `find_authorization_by_name()`: Retrieve authorization by name with optional field filtering\n- `update_authorization_expiry()`: Update authorization expiry and token\n- `search_expired_authorizations()`: Find authorizations eligible for expiry\n- `mark_authorization_as_expired()`: Update authorization status to expired\n\n**Responsibilities:**\n\n- Abstract database operations from business logic\n- Handle database errors with proper exception wrapping\n- Provide clean interface for data access\n- Support flexible field selection for performance optimization\n\n**Error Handling:**\n\n- Catches all database exceptions\n- Wraps in custom `AuthorizationError` with context\n- Maintains error logs for debugging\n\n### 3. Business Logic Layer (`AuthorizationBusinessLogic`)\n\nImplements authorization domain logic and business rules:\n\n```python\nclass AuthorizationBusinessLogic:\n    \"\"\"Business logic for authorization operations\"\"\"\n```\n\n**Key Methods:**\n\n- `extract_authorization_name_from_url()`: Parse authorization name from ACME URLs\n- `generate_authorization_token_and_expiry()`: Create new tokens with proper expiry\n- `enrich_authorization_with_identifier_info()`: Process identifier data for ACME response\n- `extract_identifier_info_for_challenge()`: Extract identifier for challenge operations\n- `is_authorization_eligible_for_expiry()`: Business rules for expiry eligibility\n\n**Responsibilities:**\n\n- Implement authorization business rules\n- Handle identifier processing and validation\n- Token generation and expiry calculation\n- Wildcard domain handling\n- TNAuthList detection and processing\n\n**Special Features:**\n\n- **Wildcard Support**: Automatic detection and processing of wildcard domains (`*.example.com`)\n- **TNAuthList Support**: Special handling for telecommunications authentication lists\n- **URL Processing**: Clean extraction of authorization names from ACME protocol URLs\n- **Expiry Logic**: Sophisticated rules for determining expiry eligibility\n\n### 4. Challenge Set Manager (`ChallengeSetManager`)\n\nManages integration with the challenge subsystem:\n\n```python\nclass ChallengeSetManager:\n    \"\"\"Manager for challenge set operations\"\"\"\n```\n\n**Responsibilities:**\n\n- Interface with the Challenge system\n- Generate appropriate challenge sets for authorizations\n- Handle TNAuth and standard challenge requirements\n- Pass through identifier information for challenge generation\n\n**Integration Points:**\n\n- Creates Challenge instances with proper configuration\n- Delegates to `challenge.challengeset_get()` for actual challenge generation\n- Manages challenge expiry alignment with authorization expiry\n\n### 5. Main Authorization Class (`Authorization`)\n\nThe primary interface for authorization operations:\n\n```python\nclass Authorization(object):\n    \"\"\"Refactored Authorization class with clear separation of concerns\"\"\"\n```\n\n**Public API Methods:**\n\n#### ACME Protocol Methods\n\n- `handle_get_request(url: str)`: Process ACME GET requests for authorization details\n- `handle_post_request(content: str)`: Process ACME POST requests for authorization updates\n- `get_authorization_details(url: str)`: Retrieve detailed authorization information\n- `expire_invalid_authorizations(timestamp: int)`: Expire authorizations past their validity\n\n#### Backward Compatibility Methods\n\n- `new_get(url: str)`: Legacy GET request handler\n- `new_post(content: str)`: Legacy POST request handler\n- `invalidate(timestamp: int)`: Legacy expiry method\n- `_authz_info(url: str)`: Legacy authorization info method\n\n**Context Manager Support:**\n\n```python\nwith Authorization(debug=True, srv_name=\"example.com\", logger=logger) as authz:\n    result = authz.handle_get_request(\"/acme/authz/abc123\")\n```\n\n**Initialization Strategy:**\n\n- Eager initialization of all components in `__init__()`\n- Configuration loading and component re-initialization in `__enter__()`\n- No cleanup required in `__exit__()`\n\n### 6. Exception Hierarchy\n\nStructured error handling with specific exception types:\n\n```python\n# Base exception\nclass AuthorizationError(Exception):\n    \"\"\"Base exception for authorization operations\"\"\"\n\n\n# Specific exceptions\nclass AuthorizationNotFoundError(AuthorizationError):\n    \"\"\"Raised when authorization is not found\"\"\"\n\n\nclass AuthorizationExpiredError(AuthorizationError):\n    \"\"\"Raised when authorization has expired\"\"\"\n\n\nclass ConfigurationError(AuthorizationError):\n    \"\"\"Raised when configuration is invalid\"\"\"\n```\n\n**Error Handling Strategy:**\n\n- Database errors wrapped in `AuthorizationError` with context\n- Configuration validation raises `ConfigurationError`\n- Clear error messages with actionable details\n- Comprehensive error logging\n\n## Design Principles\n\n### 1. Separation of Concerns\n\nEach component has a single, well-defined responsibility:\n\n- **Repository**: Database operations only\n- **BusinessLogic**: Domain rules and processing\n- **ChallengeSetManager**: Challenge system integration\n- **Authorization**: ACME protocol handling and coordination\n\n### 2. Clean Architecture\n\n```text\n┌─────────────────┐\n│   Controllers   │ ← Authorization (ACME Protocol)\n│   (Interface)   │\n└─────────────────┘\n         │\n┌─────────────────┐\n│ Business Logic  │ ← AuthorizationBusinessLogic\n│   (Use Cases)   │\n└─────────────────┘\n         │\n┌─────────────────┐\n│   Repository    │ ← AuthorizationRepository\n│  (Data Access)  │\n└─────────────────┘\n         │\n┌─────────────────┐\n│   Database      │ ← DBstore\n│ (External API)  │\n└─────────────────┘\n```\n\n### 3. Dependency Injection\n\nComponents are composed with clear dependencies:\n\n```python\n# Clean dependency chain\nconfig = AuthorizationConfig()\nrepository = AuthorizationRepository(dbstore, logger)\nbusiness_logic = AuthorizationBusinessLogic(config, repository, logger)\nchallenge_manager = ChallengeSetManager(debug, server_name, logger)\n```\n\n### 4. Immutable Configuration\n\nConfiguration is loaded once and treated as immutable:\n\n- Configuration loading separated from business logic\n- Validation at load time with clear error messages\n- Type safety with dataclass structure\n\n### 5. Comprehensive Error Handling\n\nStructured error handling at every layer:\n\n- Custom exception hierarchy with specific error types\n- Context preservation through exception chaining\n- Detailed error logging for debugging\n- ACME-compliant error responses\n\n## Key Workflows\n\n### 1. Authorization Request Processing (GET)\n\n```text\n1. Authorization.handle_get_request(url)\n2. BusinessLogic.extract_authorization_name_from_url(url)\n3. Repository.find_authorization_by_name(name)\n4. BusinessLogic.generate_authorization_token_and_expiry()\n5. Repository.update_authorization_expiry(name, token, expires)\n6. Repository.find_authorization_by_name(name, detailed_fields)\n7. BusinessLogic.enrich_authorization_with_identifier_info(auth_details)\n8. ChallengeSetManager.get_challenge_set_for_authorization(...)\n9. Return ACME-compliant authorization object\n```\n\n### 2. Authorization Update Processing (POST)\n\n```text\n1. Authorization.handle_post_request(content)\n2. [Optional] Authorization.expire_invalid_authorizations()\n3. Message.check(content) [Message validation]\n4. BusinessLogic.extract_authorization_name_from_url(protected.url)\n5. Authorization.get_authorization_details(url)\n6. Message.prepare_response(data, status)\n7. Return ACME-compliant response\n```\n\n### 3. Authorization Expiry Processing\n\n```text\n1. Authorization.expire_invalid_authorizations(timestamp)\n2. Repository.search_expired_authorizations(timestamp, fields)\n3. For each expired authorization:\n   a. BusinessLogic.is_authorization_eligible_for_expiry(auth)\n   b. Repository.mark_authorization_as_expired(auth.name)\n4. Return list of expired authorizations\n```\n"
  },
  {
    "path": "docs/architecture/certificate-architecture.md",
    "content": "# Certificate Architecture Documentation\n\n## Overview\n\nThis document describes the refactored certificate architecture and provides guidance for understanding and extending the certificate management system in acme2certifier.\n\n## Architecture Summary\n\nThe certificate subsystem implements a modular, extensible architecture using established design patterns to handle ACME certificate lifecycle management:\n\n### Design Patterns Implemented\n\n- **Repository Pattern**: Clean separation of data access logic from business logic\n- **Business Logic Layer**: Domain-specific certificate operations and business rules\n- **Configuration Pattern**: Centralized configuration management with validation\n- **Context Manager Pattern**: Resource management and initialization\n- **Exception Hierarchy**: Structured error handling with specific error types\n\n### Component Structure\n\n```text\n┌─────────────────────────────────────────────────────────────┐\n│                    Certificate Class                        │\n│               (ACME Protocol Handler)                       │\n└──────────────────────┬──────────────────────────────────────┘\n                       │\n    ┌──────────────────┼──────────────────┐\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐\n│Certificate      │ │Certificate      │ │CertificateManager   │\n│Repository       │ │BusinessLogic    │ │(Business Logic)     │\n│                 │ │                 │ │                     │\n└─────────────────┘ └─────────────────┘ └─────────────────────┘\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐\n│  DBstore        │ │CertificateData  │ │CAHandler            │\n│                 │ │                 │ │                     │\n└─────────────────┘ └─────────────────┘ └─────────────────────┘\n```\n\n## Core Components\n\n### 1. Certificate Class (`/acme_srv/certificate.py`)\n\nThe `Certificate` class is the main entry point for certificate operations and ACME protocol handling. It provides methods for:\n\n- Certificate issuance, renewal, and revocation\n- CSR storage and validation\n- Authorization and account checks\n- Logging and audit\n- Integration with CA handlers and hooks\n\n#### Key Responsibilities\n\n- Implements context manager for resource management\n- Delegates data access to the repository layer\n- Handles protocol-specific logic and error handling\n- Coordinates with CAHandler for backend CA operations\n\n### 2. Certificate Repository (`/acme_srv/db_handler.py`)\n\n- Encapsulates all database operations related to certificates\n- Provides methods for certificate CRUD, account checks, and order lookups\n- Used by the `Certificate` class for persistent storage\n\n### 3. Business Logic Layer (`/acme_srv/certificate_manager.py`)\n\n- Implements higher-level certificate operations and business rules\n- Handles certificate cleanup, expiry, and renewal logic\n- Coordinates with repository and CAHandler\n\n### 4. CA Handler (`/acme_srv/ca/ca_handler.py`)\n\n- Abstracts backend CA operations (issuance, revocation, polling)\n- Supports multiple CA backends via plugin architecture\n- Used by the `Certificate` class for all CA interactions\n\n### 5. Configuration and Helpers (`/acme_srv/helpers/`)\n\n- Centralized configuration management (`config.py`)\n- Certificate parsing, encoding, and utility functions\n- Logging, error handling, and plugin loading\n\n### 6. Error Handling\n\n- Structured exception hierarchy for certificate operations\n- Centralized error dictionary and logging\n- Graceful handling of database and CA errors\n\n## Extensibility\n\nThe certificate subsystem is designed for easy extension:\n\n- Add new CA backends by implementing the CAHandler interface\n- Extend business logic in `certificate_manager.py` for new workflows\n- Add new certificate validation or parsing helpers in `/helpers/`\n- Integrate with external systems via hooks\n\n## Sequence Example: Certificate Issuance\n\n```text\nClient Request\n     │\n     ▼\nCertificate.new_post() ──▶ Authorization/Account Check\n     │\n     ▼\n  CAHandler.issuance()\n     │\n     ▼\nCertificate._store_cert() ──▶ CertificateRepository.certificate_add()\n     │\n     ▼\n  Logging/Audit\n     │\n     ▼\n  Response to Client\n```\n"
  },
  {
    "path": "docs/architecture/challenge-architecture.md",
    "content": "# Challenge Architecture Documentation\n\n## Overview\n\nThis document describes the implemented challenge architecture and provides guidance for extending the system with new challenge types.\n\n## Architecture Summary\n\nThe refactored challenge system implements a clean, modular architecture using established design patterns:\n\n### Design Patterns Implemented\n\n- **Strategy Pattern**: Separate validation algorithms for each challenge type\n- **Registry Pattern**: Dynamic discovery and management of validators\n- **Repository Pattern**: Clean separation of data access logic\n- **State Pattern**: Challenge lifecycle management\n- **Factory Pattern**: Challenge creation and configuration\n- **Context Manager Pattern**: Resource management and initialization\n\n### Component Structure\n\n```text\n┌─────────────────────────────────────────────────────────────┐\n│                    Challenge Class                          │\n│              (ACME Protocol Handler)                        │\n└──────────────────────┬──────────────────────────────────────┘\n                       │\n    ┌──────────────────┼──────────────────┐\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌─────────────┐ ┌─────────────┐ ┌─────────────────┐\n│ChallengeRepo│ │StateManager │ │ValidatorRegistry│\n│             │ │             │ │                 │\n└─────────────┘ └─────────────┘ └─────────────────┘\n    │                  │                  │\n    │                  │                  ▼\n    │                  │        ┌─────────────────┐\n    │                  │        │   Validators    │\n    │                  │        │  ┌─────────────┐│\n    │                  │        │  │HttpValidator││\n    ▼                  ▼        │  │DnsValidator ││\n┌─────────────┐ ┌─────────────┐ │  │TlsValidator ││\n│  Database   │ │Challenge    │ │  │EmailValid...││\n│  Operations │ │Lifecycle    │ │  │TkauthValid..││\n└─────────────┘ └─────────────┘ │  └─────────────┘│\n                                └─────────────────┘\n```\n\n## Core Components\n\n### 1. Challenge Validators (`/acme_srv/challenge_validators/`)\n\nThe validator system provides modular, type-specific validation logic:\n\n```text\nchallenge_validators/\n├── __init__.py                 # Package initialization and exports\n├── base.py                     # Base classes and common structures\n├── registry.py                 # Validator registry implementation\n├── http_validator.py           # HTTP-01 challenge validator\n├── dns_validator.py            # DNS-01 challenge validator\n├── tls_alpn_validator.py       # TLS-ALPN-01 challenge validator\n├── email_reply_validator.py    # Email-reply-00 challenge validator\n├── tkauth_validator.py         # TKAuth-01 challenge validator\n├── source_address_validator.py # Source address validation\n└── README.md                   # Documentation\n```\n\n#### Base Classes\n\n- **`ChallengeValidator`**: Abstract base class defining the validation interface\n- **`ChallengeContext`**: Data structure containing challenge validation parameters\n- **`ValidationResult`**: Structured result object with success status and details\n\n#### Implemented Validators\n\n1. **`HttpChallengeValidator`**: HTTP-01 challenge validation\n1. **`DnsChallengeValidator`**: DNS-01 challenge validation\n1. **`TlsAlpnChallengeValidator`**: TLS-ALPN-01 challenge validation\n1. **`EmailReplyChallengeValidator`**: Email-reply-00 challenge validation\n1. **`TkauthChallengeValidator`**: TKAuth-01 challenge validation\n1. **`SourceAddressValidator`**: Source address validation support\n\n### 2. Business Logic Layer (`/acme_srv/challenge_business_logic.py`)\n\nHandles challenge lifecycle management and business rules:\n\n- **`ChallengeRepository`**: Database operations and data access\n- **`ChallengeStateManager`**: Challenge state transitions and lifecycle\n- **`ChallengeFactory`**: Challenge creation and configuration\n- **`ChallengeService`**: High-level business operations\n- **`ChallengeInfo`**: Challenge data structures\n\n### 3. Error Handling (`/acme_srv/challenge_error_handling.py`)\n\nComprehensive error management system:\n\n- **`ErrorHandler`**: Centralized error processing and logging\n- **`ChallengeError`**: Base exception class with error categorization\n- **Custom Exception Hierarchy**: Specific error types for different failure modes\n\n### 4. Registry Setup (`/acme_srv/challenge_registry_setup.py`)\n\nFactory functions for creating and configuring the validator registry:\n\n- **`create_challenge_validator_registry()`**: Main registry creation function\n- Configuration-driven validator registration\n- Support for optional challenge types (email, tkauth)\n\n### 5. Main Challenge Class (`/acme_srv/challenge.py`)\n\nThe Challenge class serves as the main entry point:\n\n- **Public API Methods**:\n\n  - `process_challenge_request()`: Handle ACME challenge requests\n  - `retrieve_challenge_set()`: Get or create challenge sets\n  - `challengeset_get()`: Legacy API compatibility\n  - `parse()`: Legacy API compatibility\n\n- **Context Manager Support**: Automatic resource initialization and cleanup\n\n## Design principles\n\n### 1. Modular components with clear, single responsibilities\n\n- Validators handle only validation logic\n- Repository handles only data access\n- State manager handles only lifecycle transitions\n- Factory handles only challenge creation\n\n### 2. Method Naming Clarity\n\n- `perform_validation()` - Each validator's main validation method\n- `get_challenge_details()` - Retrieve challenge information\n- `create_challenge_set()` - Create new challenges\n- `process_challenge_request()` - Handle ACME challenge requests\n- `retrieve_challenge_set()` - Get existing or create new challenges\n\n### 3. Extensibility\n\nAdding new challenge types is straightforward through the registry pattern:\n\n```python\n# Example: Adding a new challenge type\nclass NewChallengeValidator(ChallengeValidator):\n    def get_challenge_type(self) -> str:\n        return \"new-challenge-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        # Implement validation logic\n        pass\n\n\n# Register with the system\nregistry.register_validator(NewChallengeValidator(logger))\n```\n\n### 4. Error Handling\n\nStructured error handling with:\n\n- Custom exception hierarchy\n- Detailed error context and suggestions\n- ACME-compliant error responses\n- Comprehensive logging with stack traces\n\n### 5. Testing\n\nComprehensive test coverage across multiple levels:\n\n- **Unit Tests**: `test_challenge.py`\n- **Component Tests**: `test_challenge_validators.py` - Individual validator testing\n- **Business Logic Tests**: `test_challenge_business_logic.py` - Repository and service layer\n- **Error Handling Tests**: `test_challenge_error_handling.py` - Exception scenarios\n- **E2E Tests**: `test_challenge_e2e.py` - End-to-end integration testing\n\n## Creating New Challenge Types\n\nThis section provides step-by-step guidance for implementing new challenge types in the modular architecture.\n\n### Step 1: Create the Validator Class\n\nCreate a new file in `/acme_srv/challenge_validators/` following the naming convention:\n\n```python\n# /acme_srv/challenge_validators/mychallengie_validator.py\n\"\"\"\nMyChallenge-01 Challenge Validator.\n\nImplements validation logic for mychallengie-01 challenges.\n\"\"\"\nfrom .base import ChallengeValidator, ChallengeContext, ValidationResult\n\n\nclass MyChallengeValidator(ChallengeValidator):\n    \"\"\"Validator for mychallengie-01 challenges.\"\"\"\n\n    def get_challenge_type(self) -> str:\n        \"\"\"Return the challenge type identifier.\"\"\"\n        return \"mychallengie-01\"\n\n    def perform_validation(self, context: ChallengeContext) -> ValidationResult:\n        \"\"\"Perform mychallengie-01 challenge validation.\"\"\"\n        self.logger.debug(\"MyChallengeValidator.perform_validation()\")\n\n        try:\n            # Import required helpers\n            from acme_srv.helper import some_helper_function\n        except ImportError as e:\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Required dependencies not available: {e}\",\n                details={\"import_error\": str(e)},\n            )\n\n        # Implement your validation logic here\n        try:\n            # Example validation steps:\n            # 1. Extract needed information from context\n            challenge_name = context.challenge_name\n            token = context.token\n            jwk_thumbprint = context.jwk_thumbprint\n            auth_value = context.authorization_value\n\n            # 2. Perform the specific validation for your challenge type\n            validation_successful = self._perform_my_validation(\n                token, jwk_thumbprint, auth_value\n            )\n\n            # 3. Return appropriate result\n            if validation_successful:\n                return ValidationResult(\n                    success=True,\n                    invalid=False,\n                    details={\n                        \"validation_type\": \"mychallengie-01\",\n                        \"authorization_value\": auth_value,\n                        \"validated_at\": context.timestamp or time.time(),\n                    },\n                )\n            else:\n                return ValidationResult(\n                    success=False,\n                    invalid=True,\n                    error_message=\"MyChallenge validation failed\",\n                    details={\n                        \"validation_type\": \"mychallengie-01\",\n                        \"authorization_value\": auth_value,\n                        \"reason\": \"Specific failure reason here\",\n                    },\n                )\n\n        except Exception as e:\n            self.logger.error(\n                \"MyChallengeValidator.perform_validation() error: %s\", str(e)\n            )\n            return ValidationResult(\n                success=False,\n                invalid=True,\n                error_message=f\"Validation error: {str(e)}\",\n                details={\"exception\": str(e)},\n            )\n\n    def _perform_my_validation(\n        self, token: str, jwk_thumbprint: str, auth_value: str\n    ) -> bool:\n        \"\"\"Implement your specific validation logic.\"\"\"\n        # Add your challenge-specific validation code here\n        # This is where you implement the actual challenge verification\n        # according to your challenge type's specification\n\n        # Example implementation (replace with actual logic):\n        expected_response = f\"{token}.{jwk_thumbprint}\"\n        # ... perform validation steps ...\n        return True  # or False based on validation result\n```\n\n### Step 2: Register the Validator\n\nUpdate `/acme_srv/challenge_validators/__init__.py` to include your new validator:\n\n```python\n# Add import\nfrom .mychallengie_validator import MyChallengeValidator\n\n# Add to __all__ list\n__all__ = [\n    # ... existing exports ...\n    \"MyChallengeValidator\",\n]\n```\n\n### Step 3: Configure Registration\n\nUpdate `/acme_srv/challenge_registry_setup.py` to register your validator:\n\n```python\ndef create_challenge_validator_registry(\n    logger: logging.Logger, config: Optional[Dict[str, Any]] = None\n) -> ChallengeValidatorRegistry:\n    \"\"\"Create a fully configured challenge validator registry with all standard validators\"\"\"\n\n    registry = ChallengeValidatorRegistry(logger)\n\n    # Register standard ACME challenge validators\n    registry.register_validator(HttpChallengeValidator(logger))\n    registry.register_validator(DnsChallengeValidator(logger))\n    registry.register_validator(TlsAlpnChallengeValidator(logger))\n\n    # Add your new validator (conditionally if needed)\n    if config and getattr(config, \"mychallengie_support\", False):\n        registry.register_validator(MyChallengeValidator(logger))\n\n    return registry\n```\n\n### Step 4: Update Challenge Factory\n\nIf your challenge type requires special creation logic, update `/acme_srv/challenge_business_logic.py`:\n\n```python\nclass ChallengeFactory:\n    def create_challenge_set(\n        self, authorization_name: str, token: str, id_type: str, value: str, **kwargs\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Create appropriate challenge set based on configuration.\"\"\"\n        challenges = []\n\n        # ... existing challenge creation logic ...\n\n        # Add your challenge type\n        if self.config.mychallengie_support:\n            my_challenge = self.create_mychallengie_challenge(\n                authorization_name, token, value\n            )\n            if my_challenge:\n                challenges.append(my_challenge)\n\n        return challenges\n\n    def create_mychallengie_challenge(\n        self, authorization_name: str, token: str, value: str\n    ) -> Optional[Dict[str, Any]]:\n        \"\"\"Create mychallengie-01 challenge.\"\"\"\n        return self._create_single_challenge(\n            challenge_type=\"mychallengie-01\",\n            authorization_name=authorization_name,\n            token=token,\n            # Add any challenge-specific parameters\n        )\n```\n\n### Step 5: Add Configuration Support\n\nUpdate configuration handling to support your new challenge type. In the configuration system:\n\n```python\n# Example configuration option\nmychallengie_support = False  # Enable/disable your challenge type\n```\n\n### Step 6: Create Tests\n\nCreate comprehensive tests for your validator in `/test/`:\n\n```python\n# /test/test_mychallengie_validator.py\n#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Unit tests for MyChallengeValidator\"\"\"\n\nimport unittest\nfrom unittest.mock import Mock, patch\nfrom acme_srv.challenge_validators.mychallengie_validator import MyChallengeValidator\nfrom acme_srv.challenge_validators import ChallengeContext, ValidationResult\n\n\nclass TestMyChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for MyChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        self.logger = Mock()\n        self.validator = MyChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"mychallengie-01\")\n\n    def test_002_perform_validation_success(self):\n        \"\"\"Test successful validation\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        with patch.object(self.validator, \"_perform_my_validation\", return_value=True):\n            result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n\n    def test_003_perform_validation_failure(self):\n        \"\"\"Test validation failure\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        with patch.object(self.validator, \"_perform_my_validation\", return_value=False):\n            result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(result.error_message, \"MyChallenge validation failed\")\n\n    # Add more tests for edge cases, error conditions, etc.\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n```\n\n### Step 7: Documentation\n\nUpdate documentation:\n\n1. Add your validator to the README in `/acme_srv/challenge_validators/README.md`\n1. Document configuration options\n1. Add usage examples\n\n### Step 8: Integration Testing\n\nTest your new challenge type with the complete system:\n\n```python\n# Example integration test\ndef test_new_challenge_integration(self):\n    \"\"\"Test integration of new challenge type\"\"\"\n    from acme_srv.challenge_registry_setup import create_challenge_validator_registry\n\n    # Configure with your challenge enabled\n    config = Mock()\n    config.mychallengie_support = True\n\n    registry = create_challenge_validator_registry(self.logger, config)\n\n    # Verify your validator is registered\n    validator = registry.get_validator(\"mychallengie-01\")\n    self.assertIsNotNone(validator)\n    self.assertIsInstance(validator, MyChallengeValidator)\n```\n\n## Best Practices for New Challenge Types\n\n### 1. Follow the Interface Contract\n\n- Implement all required methods from `ChallengeValidator`\n- Return properly structured `ValidationResult` objects\n- Handle all error conditions gracefully\n\n### 2. Error Handling\n\n```python\n# Always handle import errors\ntry:\n    from acme_srv.helper import required_function\nexcept ImportError as e:\n    return ValidationResult(\n        success=False,\n        invalid=True,\n        error_message=f\"Required dependencies not available: {e}\",\n        details={\"import_error\": str(e)},\n    )\n\n# Catch and handle validation exceptions\ntry:\n    # validation logic\n    pass\nexcept Exception as e:\n    self.logger.error(\"Validation error: %s\", str(e))\n    return ValidationResult(\n        success=False,\n        invalid=True,\n        error_message=f\"Validation error: {str(e)}\",\n        details={\"exception\": str(e)},\n    )\n```\n\n### 3. Logging\n\n```python\n# Use structured logging with appropriate levels\nself.logger.debug(\"Starting validation for %s\", context.challenge_name)\nself.logger.info(\"Validation completed successfully\")\nself.logger.error(\"Validation failed: %s\", error_message)\n```\n\n### 4. Configuration\n\n- Make challenge types optional through configuration\n- Provide reasonable defaults\n- Document configuration options\n\n### 5. Comprehensive Testing\n\n- Test success and failure paths\n- Test error conditions and edge cases\n- Include integration tests\n- Mock external dependencies appropriately\n\n### 6. Performance\n\n- Avoid blocking operations where possible\n- Implement timeouts for network operations\n- Consider caching for expensive operations\n\n## Migration Considerations\n\n### Backward Compatibility\n\nThe refactored architecture maintains backward compatibility:\n\n- Legacy API methods (`parse()`, `challengeset_get()`) are preserved\n- Existing integrations continue to work without modification\n- Gradual migration path available\n\n### Configuration Migration\n\n- Existing configuration options continue to work\n- New configuration options are additive\n- Default behavior remains unchanged\n"
  },
  {
    "path": "docs/architecture/directory-architecture.md",
    "content": "# Directory Architecture\n\n## Overview\n\nThe `Directory` concept in acme2certifier provides a modular, extensible, and testable approach to handling ACME Directory logic, configuration, and response building. It is designed to separate concerns, facilitate maintainability, and support robust integration with external systems such as databases and CA handlers.\n\n## Components\n\n### 1. DirectoryConfig\n\nA dataclass encapsulating all configuration parameters for the Directory, including:\n\n- Version and product information\n- Terms of Service URL\n- URL prefix and home URL\n- CA identities and profiles\n- External Account Binding (EAB) support\n- Boolean flags for feature toggles\n\n### 2. DirectoryRepository\n\nHandles all database-related operations for the Directory, abstracting the underlying database handler. Key responsibilities:\n\n- Fetching the current database version\n- Logging and error handling for database access\n\n### 3. Directory\n\nThe main handler class orchestrating configuration loading, repository access, CA handler integration, and response construction. Key features:\n\n- Context manager support for safe configuration loading\n- Modular configuration parsing (sections, booleans, EAB, profiles)\n- CA handler loading and validation\n- Meta information and directory response building\n- Public API for ACME directory endpoint responses\n\n## Configuration Loading\n\nThe Directory loads its configuration from external sources using helper functions. It parses:\n\n- The `[Directory]` section for basic values\n- Boolean flags for feature toggles\n- EAB and profile settings\n- CA handler module for certificate operations\n\n## Response Construction\n\nThe Directory builds responses for the ACME directory endpoint, including:\n\n- Standard ACME endpoints (newAuthz, newNonce, newAccount, etc.)\n- Meta information (product, version, ToS, CA identities, profiles, EAB)\n- Database schema validation status\n- Randomized entries for security best practices\n\n## Extensibility & Testability\n\n- All dependencies (logger, dbstore, CA handler) are injectable for easy testing and extension.\n- Comprehensive unittests cover all major logic branches, including configuration parsing, error handling, and response generation.\n- Type annotations and docstrings improve code clarity and static analysis.\n\n## Error Handling\n\n- Centralized logging for configuration and database errors\n- Graceful fallback for missing or invalid configuration values\n- Clear error responses for CA handler issues\n\n## Security Considerations\n\n- Randomized directory entries to mitigate enumeration attacks\n- Configurable external account binding and CA identity support\n- Logging avoids exposing sensitive data\n\n## References\n\n- See other architecture docs in `docs/architecture/` for integration patterns and design principles.\n- ACME RFC 8555 for protocol details.\n"
  },
  {
    "path": "docs/architecture/order-architecture.md",
    "content": "# Order Architecture Documentation\n\n## Overview\n\nThis document describes the refactored order architecture and provides guidance for understanding and extending the order management system in acme2certifier.\n\n## Architecture Summary\n\nThe order subsystem implements a modular, extensible architecture using established design patterns to handle ACME order lifecycle management:\n\n### Design Patterns Implemented\n\n- **Repository Pattern**: Clean separation of data access logic from business logic\n- **Business Logic Layer**: Domain-specific order operations and business rules\n- **Configuration Pattern**: Centralized configuration management with validation\n- **Context Manager Pattern**: Resource management and initialization\n- **Exception Hierarchy**: Structured error handling with specific error types\n- **Data Transfer Objects**: Structured configuration and data containers\n\n### Component Structure\n\n```\n┌─────────────────────────────┐\n│         Order Class         │\n│   (ACME Protocol Handler)   │\n└─────────────┬───────────────┘\n              │\n    ┌─────────┴─────────┐\n    ▼                   ▼\nOrderRepository   OrderConfiguration\n    │                   │\n    ▼                   ▼\n  DBstore         Config/Helpers\n```\n\n## Core Components\n\n### 1. Order Class (`/acme_srv/order.py`)\n\nThe `Order` class is the main entry point for order operations and ACME protocol handling. It provides methods for:\n\n- Order creation, validation, and management\n- Authorization and profile handling\n- Configuration loading and context management\n- Logging and error handling\n- Delegation to repository and message subsystems\n\n#### Key Responsibilities\n\n- Implements context manager for resource management\n- Delegates data access to the repository layer\n- Handles protocol-specific logic and error handling\n- Coordinates with authorization and certificate subsystems\n\n### 2. Order Repository (`OrderRepository`)\n\n- Encapsulates all database operations related to orders, authorizations, accounts, and certificates\n- Provides methods for CRUD operations and lookups\n- Used by the `Order` class for persistent storage\n- Raises structured exceptions for error handling\n\n### 3. Configuration and Data Classes (`OrderConfiguration`)\n\n- Centralized configuration management for order handling\n- Stores validity periods, feature toggles, limits, and profile settings\n- Supports dynamic loading from config files and database\n- Used by the `Order` class for runtime configuration\n\n### 4. Error Handling\n\n- Structured exception hierarchy for order operations (`OrderDatabaseError`, `OrderValidationError`)\n- Centralized error dictionary and logging\n- Graceful handling of database and validation errors\n\n## Extensibility\n\nThe order subsystem is designed for easy extension:\n\n- Add new business rules or validation logic in the `Order` class\n- Extend repository methods for new database operations\n- Add new configuration options in `OrderConfiguration`\n- Integrate with external systems via hooks or message handlers\n\n## Sequence Example: Order Creation\n\n```\nClient Request\n     │\n     ▼\nOrder.create_order() ──▶ Identifier/Profile Validation\n     │\n     ▼\nOrderRepository.add_order()\n     │\n     ▼\nOrder._add_authorizations_to_db()\n     │\n     ▼\nOrderRepository.add_authorization()\n     │\n     ▼\nLogging/Audit\n     │\n     ▼\nResponse to Client\n```\n\n## File Locations\n\n- Order logic: `/acme_srv/order.py`\n- Repository: `/acme_srv/order.py` (OrderRepository)\n- Configuration: `/acme_srv/order.py` (OrderConfiguration)\n- Helpers: `/acme_srv/helper.py`, `/acme_srv/db_handler.py`\n- Error handling: `/acme_srv/order.py`, `/acme_srv/error.py`\n\n______________________________________________________________________\n"
  },
  {
    "path": "docs/architecture/renewalinfo-architecture.md",
    "content": "# Renewalinfo Architecture Documentation\n\n## Overview\n\nThis document describes the architecture of the refactored Renewalinfo subsystem, which manages ACME renewal information. The design emphasizes separation of concerns, robust error handling, and testability.\n\n## Architecture Summary\n\nThe Renewalinfo subsystem implements a modular, maintainable architecture using established design patterns:\n\n### Design Patterns Implemented\n\n- **Repository Pattern**: Encapsulates all data access logic for certificates and housekeeping parameters.\n- **Configuration Object Pattern**: Centralizes configuration management using a dataclass.\n- **Context Manager Pattern**: Ensures proper initialization and resource management.\n- **Separation of Concerns**: Distinct classes for business logic, configuration, and data access.\n- **Mockable Interfaces**: All external dependencies are easily mockable for testing.\n\n### Component Structure\n\n```text\n┌─────────────────────────────────────────────────────────────┐\n│                    Renewalinfo Class                        │\n│              (ACME Renewal Info Handler)                    │\n└──────────────────────┬──────────────────────────────────────┘\n                       │\n    ┌──────────────────┼──────────────────┐\n    │                  │                  │\n    ▼                  ▼                  ▼\n┌──────────────┐ ┌──────────────┐ ┌────────────────────┐\n│ RenewalinfoConfig │ │ Repository     │ │   Message/Logger    │\n│                    │ │ (DB access)    │ │ (Error, Logging)    │\n└──────────────┘ └──────────────┘ └────────────────────┘\n                       │\n                       ▼\n                ┌──────────────┐\n                │   Database   │\n                └──────────────┘\n```\n\n## Core Components\n\n### 1. RenewalinfoConfig (`acme_srv/renewalinfo.py`)\n\n- **Purpose**: Holds all configuration parameters for renewal logic (e.g., renewal_force, threshold, retry timeout).\n- **Implementation**: Python dataclass for type safety and clarity.\n\n### 2. RenewalinfoRepository (`acme_srv/renewalinfo.py`)\n\n- **Purpose**: Encapsulates all database access for certificates and housekeeping parameters.\n- **Responsibilities**:\n  - Certificate lookup by certid or serial/AKI\n  - Adding certificates\n  - Housekeeping parameter management\n- **Benefits**: Clean separation from business logic, easy to mock for testing.\n\n### 3. Renewalinfo (Main Handler, `acme_srv/renewalinfo.py`)\n\n- **Purpose**: Implements all business logic for ACME renewal info endpoints.\n- **Responsibilities**:\n  - Loads and manages configuration\n  - Handles context management (with statement)\n  - Orchestrates certificate lookups and renewal window calculation\n  - Provides public `get()` and `update()` methods for API compatibility\n  - Centralizes error handling and logging\n- **Design**: Delegates all data access to the repository and all configuration to the config object.\n\n### 4. Message and Logger\n\n- **Purpose**: Handles error messages, logging, and protocol-specific message parsing.\n- **Integration**: Passed as dependencies to Renewalinfo for full testability.\n\n## Key Flows\n\n### Certificate Lookup and Renewal Info Generation\n\n- **Request Handling**: Public `get()` method receives a URL, parses the renewal info string.\n- **Housekeeping**: Ensures certificate table is up-to-date (triggers update if needed).\n- **Certificate Lookup**: Uses repository to find certificate by certid or serial/AKI.\n- **Renewal Window Calculation**: Computes suggested renewal window based on config and certificate data.\n- **Response Construction**: Returns structured response with renewal info or error details.\n\n### Configuration Loading\n\n- Loads from config file using a harmonized approach.\n- All parsing errors are logged and fallback values are used.\n\n### Error Handling\n\n- All database and configuration errors are logged with context.\n- Fallbacks and safe defaults are used to ensure robust operation.\n\n## Extensibility\n\n- **New Data Sources**: Add methods to the repository.\n- **New Business Rules**: Extend the main handler logic.\n- **Testing**: All dependencies are mockable; comprehensive unittests are provided.\n\n## File Structure\n\n```text\nacme_srv/\n├── renewalinfo.py         # Main handler, config, repository\n├── db_handler.py          # Database abstraction\n├── message.py             # Protocol message handling\n```\n"
  },
  {
    "path": "docs/asa.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA handler for Insta -->\n\n# Connecting to Insta ActiveCMS\n\n## Prerequisites\n\n- ActiveCMS needs to have the Active Security API activated.\n- You need to have a user, password, and an API key to access the ASA.\n- You need to have permissions to revoke and enroll certificates.\n\n## Configuration\n\nModify the server configuration (`/acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/asa_ca_handler.py\napi_host: http://<ip>:<port>\napi_user: <user>\napi_password: <password>\napi_key: <api_key>\nca_bundle: <value>\nca_name: <ca_name>\nprofile_name: <value>\ncert_validity_days: <days>\n```\n\n### Parameter Descriptions\n\n- `api_host` - URL of the Active Security API.\n- `api_user` - REST user.\n- `api_user_variable` - *Optional* - Name of the environment variable containing the REST username (a configured `api_user` parameter in `acme_srv.cfg` takes precedence).\n- `api_password` - Password for the REST user.\n- `api_password_variable` - *Optional* - Name of the environment variable containing the password for the REST user (a configured `api_password` parameter in `acme_srv.cfg` takes precedence).\n- `api_key` - Key for the REST user.\n- `api_key_variable` - *Optional* - Name of the environment variable containing the API key for REST access (a configured `api_key` parameter in `acme_srv.cfg` takes precedence).\n- `ca_bundle` - Certificate bundle needed to validate the server certificate. Can be `True`/`False` or a filename (default: `None`).\n- `ca_name` - Name of the CA used to enroll certificates.\n- `profile_name` - Profile name.\n- `cert_validity_days` - *Optional* - Polling timeout (default: `60s`).\n- `enrollment_config_log` - *Optional* - Log enrollment parameters (default: `False`).\n- `enrollment_config_log_skip_list` - *Optional* - List of enrollment parameters not to be logged, in JSON format. Example: `[\"parameter1\", \"parameter2\"]` (default: `[]`).\n- `allowed_domainlist` - *Optional* - List of domain names allowed for enrollment, in JSON format. Example: `[\"bar.local$\", \"bar.foo.local\"]` (default: `[]`).\n\n### Increase Enrollment Timeout\n\nIt is recommended to increase the enrollment timeout to prevent `acme2certifier` from closing the connection too early.\n\n```config\n[Certificate]\nenrollment_timeout: 15\n```\n\n### Retrieving CA and Profile Information\n\nYou can retrieve the list of certificate authorities by running the following REST call against ASA:\n\n```bash\nroot@rlh:~# curl -u \"$api_user\":\"$api_password\" -H \"x-api-key: <api_key>\" $api_host'/list_issuers'\n```\n\nYou can retrieve the list of profiles by running the following REST call against ASA (the `ca_name` parameter must be [URL-encoded](https://en.wikipedia.org/wiki/Percent-encoding)):\n\n```bash\nroot@rlh:~# curl -u \"$api_user\":\"$api_password\" -H \"x-api-key: <api_key>\" $api_host'/list_profiles?issuerName=<ca_name>'\n```\n\nThe CA handler will verify the `ca_name` and `profile_name` parameters before enrollment.\n\n## Passing a Profile ID from Client to Server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `profile_name` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}\n```\n\nOnce enabled, a client can specify the profile_name to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile profile2\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md), allowing an ACME client to specify a `profile_name` to be used during certificate enrollment. This feature is disabled by default and must be activated in `acme_srv.cfg` as shown below:\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe ACME client can then specify the `profile_name` as part of its user-agent string.\n\n### Example for acme.sh\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent profile_name=<profile-name> --debug 3 --output-insecure\n```\n\n### Example for Lego\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent profile_name=<profile_name> -d <fqdn> --http run\n```\n"
  },
  {
    "path": "docs/async_mode.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title Asynchronous Mode (`async_mode`) in acme2certifier -->\n\n# Asynchronous Mode (`async_mode`) in acme2certifier\n\n## Overview\n\n`async_mode` is a configuration parameter that enables asynchronous processing for certificate enrollment and challenge validation in acme2certifier. Once enabled, certain operations (such as ACME challenge validation and certificate enrollment) are executed in background threads, allowing the API to respond immediately and process requests without blocking.\n\n## Enabling `async_mode`\n\n`async_mode` is enabled via the configuration file (typically `acme_srv.cfg`).\n\n**Example configuration:**\n\n```ini\n[DEFAULT]\nasync_mode = True\n```\n\n### Requirements for Enabling\n\n- **Database Handler:** You must use the [Django database handler](../examples/db_handler/django_handler.py) for asynchronous mode to work.\n- **Database Backend:** The Django handler must be configured to use either a [MariaDB or PostgreSQL backend](external_database_support.md).\n\n**Why Django Backend is Required:**\n\nThe Django backend is required for `async_mode` because it provides:\n\n- More robust transaction management\n- Connection pooling\n- Thread safety\n- **Concurrent write access**\n\nThese features are essential for reliable asynchronous operations. MariaDB and PostgreSQL, when used with Django's ORM, support concurrent access and atomic transactions, ensuring that background threads can safely read and write to the database without risking data corruption or race conditions. The default WSGI backend unfortuately lacks these guarantees, which can lead to unpredictable behavior or data loss in asynchronous workflows.\n\nHence, when using the WSGI-handler, `async_mode` will be ignored and the application will fall back to synchronous processing. The system logs a message if you attempt to enable async mode without the required backend:\n\n> \"asynchronous Challenge validation disabled, requires django db handler\"\n"
  },
  {
    "path": "docs/ca_handler.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title How to Create Your Own CA Handler -->\n\n# How to Create Your Own CA Handler\n\nCreating your own CA handler should be straightforward. All you need to do is create a `ca_handler.py` file with a `CAhandler` class that contains the following methods required by `acme2certifier`:\n\n- **`enroll`**: Enrolls a new certificate from the CA server.\n- **[`poll`](poll.md)**: Polls a pending certificate request from the CA server.\n- **`revoke`**: Revokes an existing certificate on the CA server.\n- **[`trigger`](trigger.md)**: Processes triggers sent by the CA server.\n\nThe [`skeleton_ca_handler.py`](../examples/ca_handler/skeleton_ca_handler.py) file provides a template that you can use to create customized CA handlers.\n\nThe following skeleton outlines the input parameters received by `acme2certifier`, as well as the expected return values:\n\n```python\nclass CAhandler:\n    \"\"\"CA handler\"\"\"\n\n    def __init__(self, debug=None, logger=None):\n        \"\"\"\n        Input:\n            debug - Debug mode (True/False)\n            logger - Log handler\n        \"\"\"\n        self.debug = debug\n        self.logger = logger\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a context manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Closes the connection at the end of the context\"\"\"\n        pass\n\n    def enroll(self, csr):\n        \"\"\"Enrolls a certificate\"\"\"\n        # Input:\n        #     csr - CSR in PKCS#10 format\n\n        # Output:\n        #     error - Error message during certificate enrollment (None if no error occurred)\n        #     cert_bundle - Certificate chain in PEM format\n        #     cert_raw - Certificate in ASN.1 (binary) format, base64 encoded\n        #     poll_identifier - Callback identifier to track enrollment requests when the CA server does not\n        #                       issue certificates immediately.\n\n        self.logger.debug(\"Certificate.enroll()\")\n        ...\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return None, None, None, None\n\n    def poll(self, cert_name, poll_identifier, csr):\n        \"\"\"Polls the status of a pending CSR and downloads certificates\"\"\"\n        # Input:\n        #     cert_name - Certificate resource name\n        #     poll_identifier - Poll identifier\n        #     csr - Certificate Signing Request\n\n        # Output:\n        #     error - Error message during certificate polling (None if no error occurred)\n        #     cert_bundle - Certificate chain in PEM format\n        #     cert_raw - Certificate in ASN.1 (binary) format, base64 encoded\n        #     poll_identifier - Updated callback identifier for future lookups\n        #     rejected - Indicates whether the request has been rejected by the CA administrator.\n\n        self.logger.debug(\"CAhandler.poll()\")\n        ...\n        return None, None, None, None, False\n\n    def revoke(self, cert, rev_reason=\"unspecified\", rev_date=None):\n        \"\"\"Revokes a certificate\"\"\"\n        # Input:\n        #     cert - Certificate in PEM format\n        #     rev_reason - Revocation reason\n        #     rev_date - Revocation date\n\n        # Output:\n        #     code - HTTP status code to be returned to the client\n        #     message - Error message if applicable, None otherwise\n        #     detail - Additional error details\n\n        self.logger.debug(f\"CAhandler.revoke({rev_reason}: {rev_date})\")\n        ...\n        return 200, None, None\n\n    def trigger(self, payload):\n        \"\"\"Processes triggers sent by the CA server\"\"\"\n        # Input:\n        #     payload - Payload content\n\n        # Output:\n        #     error - Error message (if something went wrong)\n        #     cert_bundle - Certificate chain in PEM format\n        #     cert_raw - Certificate in ASN.1 (binary) format, base64 encoded\n\n        self.logger.debug(\"CAhandler.trigger()\")\n        ...\n        self.logger.debug(\"CAhandler.trigger() ended with error: {0}\".format(error))\n        return (error, cert_bundle, cert_raw)\n```\n\n## Additional Customization\n\nYou can add additional methods as needed. Additionally, you can configure `acme_srv.cfg` to customize the behavior of the CA handler.\n\nFor further details, check [`certifier_ca_handler.py`](../examples/ca_handler/certifier_ca_handler.py), especially the `_config_load()` method.\n"
  },
  {
    "path": "docs/cert-mgr.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Using cert manager to enroll certificate in Kubernetes environments -->\n\n# Using cert-manager to enroll certificate in Kubernetes environments\n\nI do not really have a full Kubernetes environment. Thus, I was using [https://microk8s.io/](https://microk8s.io/) for testing.\n\n## Prerequisites\n\n- cert-manager must be installed. See [instructions](https://cert-manager.io/docs/installation/kubernetes/) for further information. (I was installing with regular manifest but did change to helm to ensure that I always use the latest version)\n\n## Issuer configuration\n\nThe below steps based on instructions taken from [cert-manager documentation](https://cert-manager.io/docs/configuration/acme/). Cert-manager can run as `Issuer` or `ClusterIssuer` resource. The below configuration example uses `Issuer` resource; a `ClusterIssuer` configuration is part of the [release regression](../.github/k8s-cert-mgr-http-01.yml) testing both `http-01` and `dns-01` challenge validation.\n\n- Create an issuer configuration file as below\n\n```bash\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: cert-manager-acme\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: acme2certifier\n  namespace: cert-manager-acme\nspec:\n  acme:\n    email: foo@bar.local\n    server: http://192.168.14.1/directory\n    privateKeySecretRef:\n      # Secret resource that will be used to store the account's private key.\n      name: issuer-account-key\n    # Add a single challenge solver, HTTP01 using nginx\n    solvers:\n    - http01:\n        ingress:\n          class: nginx\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: acme-cert\n  namespace: cert-manager-acme\nspec:\n  secretName: k8-acme-secret\n  issuerRef:\n    name: acme2certifier\n  dnsNames:\n    - k8-acme.bar.local\n  # optional but recommended to avoid reenrollment loops in case of short certificate lifetimes\n  renewBefore: 48h\n```\n\n- apply the configuration. Certificate enrollment should start immediately\n\n```bash\ngrindsa@ub-20:~$ microk8s.kubectl apply -f acme2certifier.yaml\n```\n\n- the enrollment status can be checked via `microk8s.kubectl describe certificate -n cert-manager-acme`\n\n```bash\ngrindsa@ub-20:~$ microk8s.kubectl describe certificate -n cert-manager-acme\nName:         acme-cert\nNamespace:    cert-manager-acme\nLabels:       <none>\nAnnotations:  API Version:  cert-manager.io/v1alpha3\nKind:         Certificate\n...\nSpec:\n  Dns Names:\n    k8-acme.bar.local\n  Issuer Ref:\n    Name:       acme2certifier\n  Secret Name:  acme2certifier-secret\nStatus:\n  Conditions:\n    Last Transition Time:  2020-06-28T07:36:05Z\n    Message:               Certificate is up to date and has not expired\n    Reason:                Ready\n    Status:                True\n    Type:                  Ready\n  Not After:               2021-06-28T07:35:53Z\nEvents:\n  Type    Reason        Age   From          Message\n  ----    ------        ----  ----          -------\n  Normal  GeneratedKey  60s   cert-manager  Generated a new private key\n  Normal  Requested     60s   cert-manager  Created new CertificateRequest resource \"acme-cert-3129588559\"\n  Normal  Issued        58s   cert-manager  Certificate issued successfully\n```\n\n- the certificate details can be checked by using the command `microk8s.kubectl get certificate acme-cert -o yaml -n cert-manager-acme`\n- You can check the private key with `microk8s.kubectl get secret acme-cert-key -o yaml -n cert-manager-acme`. You should see a base64 encoded key in the `tls.key` field.\n- certificate, issuer and namespace can be deleted with `microk8s.kubectl delete -f acme2certifier.yaml`\n\n# Troubleshooting\n\nThere are [extensive troubleshooting guides at the cert-manager website](https://cert-manager.io/docs/faq/acme/).\n\nBelow is a list of commands I that I found most useful:\n\n- `kubectl get order -n <name-space>` - to get the list of orders\n- `kubectl describe order -n <name-space> <order>` - to display the details of an order\n- `kubectl describe challenge -n <name-space>` - show challenges and provisioning status\n"
  },
  {
    "path": "docs/certifier.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler for NetGuard Certificate Manager and Insta Certifier -->\n\n# Connecting to Insta Certifier\n\n## Prerequisites\n\n- the Certifier needs to have the REST-service activated\n- you have a user and password to access Certifier via REST-Service\n\n## Configuration\n\n- modify the server configuration (`/acme_srv/acme_srv.cfg`) and add the following parameters\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/certifier_ca_handler.py\napi_host: http://<ip>:<port>\napi_user: <user>\napi_password: <password>\nca_bundle: <value>\nca_name: <ca_name>\nprofile_id: <value>\npolling_timeout: <seconds>\n```\n\n- api_host - URL of the Certifier REST service\n- api_user - REST user\n- api_user_variable - *optional* - name of the environment variable containing the REST username (a configured `api_user` parameter in acme_srv.cfg takes precedence)\n- api_password - password for REST user\n- api_password_variable - *optional* - name of the environment variable containing the password for the REST user (a configured `api_password` parameter in acme_srv.cfg takes precedence)\n- ca_bundle - optional - certificate bundle needed to validate the server certificate - can be True/False or a filename (default: True)\n- ca_name - name of the CA used to enroll certificates\n- allowed_domainlist - optional - list of domain-names allowed for enrollment in json format example: \\[\"bar.local$, bar.foo.local\\] (default: \\[\\])\n- enrollment_config_log - optional - log enrollment parameters (default False)\n- enrollment_config_log_skip_list - optional - list enrollment parameters not to be logged in json format example: \\[ \"parameter1\", \"parameter2\" \\] (default: \\[\\])\n- profile_id - optional - profileId\n- polling_timeout - optional - polling timeout (default: 60s)\n\nDepending on CA policy configuration a CSR may require approval. In such a situation acme2certifier will poll the CA server to check the CSR status. The polling interval can be configured in acme.server.cfg.\n\nYou can get the `ca_name` by running the following REST call against certifier.\n\n```bash\nroot@rlh:~# curl -u '$api_user':'$api_password' $api_host'/v1/cas\n```\n\nThe response to this call will return a dictionary containing the list of CAs including description and name. Pick the value in the \"name\" field.\n\n```REST\n  \"offset\": 0,\n  \"limit\": 50,\n  \"totalCount\": 3,\n  \"href\": \"<url>\",\n  \"cas\": [\n    {\n      \"href\": \"<url>/v1/cas/kQg0moMYAHGyG7jrQeT2Fw\",\n      \"name\": \"Insta Certifier Internal CA\",\n      \"description\": \"CA for Certifier internal TLS communication and operational use\",\n      \"status\": \"active\",\n      \"type\": \"online\",\n      \"certificates\": {\n        \"active\": \"<url>/v1/certificates/JPnxc-OqxkXdQt6An2vqnw\"\n      }\n    },\n    {\n      \"href\": \"\"<url>/v1/cas/PnOBdgHSiz5c1sR0MsZMtw\",\n      \"name\": \"ca_name\",\n      \"description\": \"Test CA for acme2certifier\",\n      \"status\": \"active\",\n      \"type\": \"online\",\n      \"certificates\": {\n        \"active\": \"<url>/v1/certificates/Ur-YAdXw6S8ddGl7ITVTjA\"\n      }\n    }\n  ]\n```\n\n## Passing a profile_id from client to server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `profile_id` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"101\": \"http://foo.bar/profile101\", \"102\": \"http://foo.bar/profile102\", \"103\": \"http://foo.bar/profile103\"}\n```\n\nOnce enabled, a client can specify the profile_id to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile 102\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md) allowing an ACME client to specify a profile_id to be used during certificate enrollment. This feature is disabled by default and must be activated in `acme_srv.cfg` as shown below\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe ACME client can then specify the profileID as part of its user-agent string.\n\nExample for acme.sh:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent profile_id=101 --debug 3 --output-insecure\n```\n\nExample for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent profile_id=101 -d <fqdn> --http run\n```\n\n# eab profiling\n\nThis handler can use the [eab profiling feature](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activatedd in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nBelow is an example key-file used during regression testing:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"profile_id\": [\"p100\", \"p101\", \"p102\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"]\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"profile_id\": \"102\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"ca_name\": \"subca2\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n\n## CA policy configuration\n\nA CSR generated by certbot client does not contain any subject name. Such a CSR will be refused by Certifier. To overcome this, you need a CA policy as below setting a subject name.\n\n```policy\n(policy\n  (receive-request\n    (set-validity-period\n      (null)\n      (length 30)\n      (type 86400)\n      (end-of-day #f)\n      (overwrite #t))\n    (issue-automatic\n      (null)\n      (mode all))\n    (issue-manual\n      (null)))\n  (accept-request\n    (conditional-policy\n      (null)\n      (clause\n        (test\n          (module match-subject-name)\n          (match-subject-name\n            (null)\n            (pattern)\n            (prefix #f)\n            (invert-match #f)))\n        (chain\n          (set-subject-name\n            (null)\n            (format \"CN=%{altname:dns}\")))))\n    (set-validity-period\n      (null)\n      (length 1)\n      (type 2592000)\n      (end-of-day #t)\n      (overwrite #t))\n    (add-aia\n      (null)\n      (url http://aia_path/))\n    (set-crl-distribution-point\n      (null))\n    (accept-all\n      (null)))\n  (view-request\n    (accept-all\n      (null)))\n  (update-request\n    (accept-all\n      (null))))\n```\n\nIMPORTANT: the above policy will configure a certificate lifetime of 30 days only. Please review carefully and modify according to your needs.\n"
  },
  {
    "path": "docs/cmp.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler using CMPv2 protocol -->\n\n# Generic CMPv2 Protocol Handler\n\nThe CMPv2 protocol handler is not bound to a specific CA server. Certificate enrollment is done using the [CMP application from OpenSSL 3.x](https://www.openssl.org/docs/manmaster/man1/openssl-cmp.html).\nThis handler acts as a wrapper that calls OpenSSL with specific parameters using the `subprocess` module.\nAs of today, revocation operations are not supported.\n\nThe handler has been tested against [Insta Certifier](https://www.insta.fi/en/services/cyber-security/insta-certifier).\n\n## Prerequisites\n\nYou need a system using OpenSSL 3.x or higher.\n\nTechnically, the CA handler acts as a registration authority (RA) towards the CMPv2 server. This means you need to configure a registration authority on your CMPv2 server with either Refnum/PSK or certificate authentication. Please check your CA server documentation on how to do this.\n\nThe configuration can be a bit tricky and may require fine-tuning depending on the type and setup of your CMPv2 server. I strongly suggest trying enrollment via the command line first and adapting the CA handler accordingly.\n\nIn my setup, acme2certifier authenticates via Refnum/Secret towards the CMPv2 server. Certificate-based authentication is also supported. The CA handler configuration described below maps to the following command-line command:\n\n```shell\ngrindsa@ub-22:~/a2c$ openssl.exe cmp -cmd ir -server 192.168.14.137:8080 -path pkix/ -ref 1234 -secret pass:xxx -recipient \"/C=DE/CN=tst_sub_ca\" -cert ra_cert.pem -trusted capubs.pem -popo 0 -ignore_keyusage -extracertsout ca_certs.pem -certout test-cert.pem -csr csr.pem\n```\n\n| Parameter        | Value                 | Description                                                                                                                                                |\n| :--------------- | :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| -cmd             | ir                    | Request type \"initial request\"                                                                                                                             |\n| -server          | 192.168.14.137:8080   | Address and port of the CMPv2 server                                                                                                                       |\n| -path            | pkix/                 | Path on the CMPv2 server                                                                                                                                   |\n| -ref             | 1234                  | Reference number used for authentication towards the CMPv2 server                                                                                          |\n| -ref_variable    | CMPV2_REF             | Name of the environment variable containing the reference number used for authentication (a configured `ref` parameter in `acme_srv.cfg` takes precedence) |\n| -secret          | pass:xxx              | Secret used for authentication towards the CMPv2 server                                                                                                    |\n| -secret_variable | CMPV2_SECRET          | Name of the environment variable containing the authentication secret (a configured `secret` parameter in `acme_srv.cfg` takes precedence)                 |\n| -recipient       | \"/C=DE/CN=tst_sub_ca\" | DN of the issuing CA                                                                                                                                       |\n| -cert            | ra_cert.pem           | Public key of the local registration authority                                                                                                             |\n| -trusted         | capubs.pem            | CA certificate bundle needed to verify the CMPv2 server certificate                                                                                        |\n| -popo            | 0                     | Set the RA verified Set Proof-of-Possession (POPO) method to \"RA verified\"                                                                                 |\n| -extracertsout   | ca_certs.pem          | File containing the CA certificates extracted from the CMPv2 response                                                                                      |\n| -certout         | test-cert.pem         | File containing the certificate returned from the CA server                                                                                                |\n| -csr             | csr.pem               | CSR to be imported                                                                                                                                         |\n\nThe latest version of the documentation for the OpenSSL CMP application can be found [here](https://www.openssl.org/docs/manmaster/man1/openssl-cmp.html).\n\n## Installation and Configuration\n\n- Note down the OpenSSL command line for a successful certificate enrollment.\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/cmp_ca_handler.py\n```\n\n- Modify the server configuration (`/acme_srv/acme_srv.cfg`) according to your needs. Every parameter used in the OpenSSL CLI command requires a corresponding entry in the `[CAhandler]` section. The entry should be the name of the OpenSSL parameter with the prefix `cmp_`, and the value should match the parameter used in the OpenSSL CLI command. You can also customize the path to your OpenSSL 3.x binary (`cmp_openssl_bin`).\n\nThe CLI command mentioned above will result in the following configuration to be inserted into `acme_srv.cfg`:\n\n```config\n[CAhandler]\ncmp_server: 192.168.14.137:8080\ncmp_path: pkix/\ncmp_cert: acme_srv/cmp/ra_cert.pem\ncmp_ref: 1234\ncmp_secret: pass:xxx\ncmp_trusted: acme_srv/cmp/capubs.pem\ncmp_recipient: C=DE, CN=tst_sub_ca\ncmp_ignore_keyusage: True\n```\n\nThe parameters `-cmp ir` and `-popo 0` are set by the CA handler, so there is no need to specify these in the config. The same applies to the `-extracertsout` and `-certout` options, which will be set by the handler at runtime.\n\nHappy enrolling! :-)\n"
  },
  {
    "path": "docs/digicert.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler for Digicert CertCentral -->\n\n# Connecting to DigiCert CertCentral\n\nThis handler can be used to enroll certificates from [DigiCert CertCentral](https://dev.digicert.com/en/certcentral-apis.html).\n\n## Prerequisites\n\n- you'll need:\n  - a DigiCert CertCentral subscription :-)\n  - an [API-Key](https://dev.digicert.com/en/certcentral-apis/authentication.html) for Authentication and Authorization\n  - an [Organization](https://dev.digicert.com/en/certcentral-apis/services-api/organizations.html)\n  - a [whitelisted domain](https://dev.digicert.com/en/certcentral-apis/services-api/domains.html)\n\n## Configuration\n\n- modify the server configuration (`acme_srv.cfg`) and add the first thre of the below mentioned parameters\n\n```confag\n[CAhandler]\nhandler_file: examples/ca_handler/digicert_ca_handler.py\napi_key: <api_key>\norganization_name: <organization_name>\n\nallowed_domainlist: <allowed_domainlist>\napi_url: <api_url>\norganization_id: <organization_id>\ncert_type: <cert_type>\nsignature_hash: <signature_hash>\norder_validity: <order_validity>\nrequest_timeout: <seconds>\n```\n\n- api_key - required - API key to access the API\n- organization_name - required - Organization name as specified in DigiCert CertCentral\n- allowed_domainlist: list of domain-names allowed for enrollment in json format (example: \\[\"bar.local$, bar.foo.local\\])\n- api_url - optional - URL of the CertCentral API\n- organization_id - optional - organization id - configuration prevents additional rest-lookups\n- cert_type - optional - [certificte type](https://dev.digicert.com/en/certcentral-apis/services-api/orders.html) to be isused. (default: ssl_basic)\n- signature_hash - optional - hash algorithm used for certificate signing - (default: sha256)\n- order_validity - optional - oder validity (default: 1 year)\n- request_timeout - optional - requests timeout in seconds for requests (default: 5s)\n- allowed_domainlist - optional - list of domain-names allowed for enrollment in json format example: \\[\"bar.local$, bar.foo.local\\] (default: \\[\\])\n- enrollment_config_log - optional - log enrollment parameters (default False)\n- enrollment_config_log_skip_list - optional - list enrollment parameters not to be logged in json format example: \\[ \"parameter1\", \"parameter2\" \\] (default: \\[\\])\n\nUse your favorite acme client for certificate enrollment. A list of clients used in our regression can be found in the [disclaimer section of our README file](../README.md)\n\n*Important:* the DigiCert API expectes a CommonName to be set. Hence, certbot cannot be used for certificate enrollment.\n\n## Passing a cert_type from client to server\n\nacme2certifier supports the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a [cert_type](https://dev.digicert.com/en/certcentral-apis/services-api/orders.html) parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/ssl_basic\", \"profile2\": \"http://foo.bar/ssl_securesite_pro\", \"profile3\": \"http://foo.bar/ssl_secure\"}\n```\n\nOnce enabled, a client can specify the cert_type to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile ssl_securesite_pro\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md) allowing an acme-client to specify a [certificate type](https://dev.digicert.com/en/certcentral-apis/services-api/orders.html) to be used during certificate enrollment. This feature is disabled by default and must be activate in `acme_srv.cfg` as shown below\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe acme-client can then specify the cert_type as part of its user-agent string.\n\nExample for acme.sh:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent cert_type=ssl_securesite_pro --debug 3 --output-insecure\n```\n\nExample for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent cert_type=ssl_securesite_pro -d <fqdn> --http run\n```\n\n# eab profiling\n\nThis handler can use the [eab profiling feture](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activated in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nbelow an example key-file used during regression testing:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"cert_type\": [\"ssl_basic\", \"ssl_securesite_pro\", \"ssl_securesite_flex\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"organization_name\": \"acme2certifier\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"cert_type\": \"ssl_securesite_pro\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/eab.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: External Account Binding -->\n\n# External Account Binding\n\nExternal Account Binding (EAB) allows an ACME account to use authorizations granted to an external, non-ACME account. This enables `acme2certifier` to handle issuance scenarios that cannot yet be fully automated, such as issuing Extended Validation (EV) certificates.\n\nTo enable EAB, the Certificate Authority (CA) operator must provide both the ACME client and `acme2certifier` with a Key Identifier (`kid`) and a MAC key (`mac_key`). These credentials authenticate `NewAccount` requests.\n\n`kid` and `mac_key` are loaded into `acme2certifier` via a plugin-based mechanism. By default, two plugins are available in the `example/eab_handler` directory.\n\nKey identifiers are included in reports generated by the [Housekeeping](housekeeping.md) class.\n\nBy default `acme2certifier` validates, during each ACME transaction, whether the EAB credentials used to create the ACME account remain valid. If this check fails, `acme2certifier` stops processing the transaction. This check can be disabled by the configuration option `eabkid_check_disable` in `acme_srv.cfg`.\n\n```ini\n[EABhandler]\n...\neabkid_check_disable: True\n```\n\n## File Handler\n\nThe `eab_file_handler.py` script allows `kid` and `mac_key` to be loaded from a CSV file. To activate this handler, configure the `EABhandler` section in `acme_srv.cfg` as follows:\n\n```ini\n[EABhandler]\neab_handler_file: examples/eab_handler/file_handler.py\nkey_file: examples/eab_handler/key_file.csv\n```\n\nThe `key_file` must be in CSV format, with `kid` in the first column and `mac_key` (Base64 encoded) in the second column:\n\n```csv\neab_kid,eab_mac\nkeyid_00,bWFjXz...Aw\nkeyid_01,bWFjXz...Ax\nkeyid_02,bWFjXz...Ay\nkeyid_03,bWFjXz...Az\n```\n\n## JSON Handler\n\nThe `eab_json_handler.py` script allows `kid` and `mac_key` (Base64 encoded) to be loaded from a JSON file. To activate this handler, configure the `EABhandler` section in `acme_srv.cfg` as follows:\n\n```ini\n[EABhandler]\neab_handler_file: examples/eab_handler/json_handler.py\nkey_file: examples/eab_handler/key_file.json\n```\n\nThe `key_file` should contain key-value pairs in JSON format:\n\n```json\n{\n  \"keyid_00\": \"bWFjXz...Aw\",\n  \"keyid_01\": \"bWFjXz...Ax\",\n  \"keyid_02\": \"bWFjXz...Ay\",\n  \"keyid_03\": \"bWFjXz...Az\"\n}\n```\n\n## SQL Handler\n\nThe `sql_handler.py` script allows `kid` and `mac_key` (Base64 encoded) to be loaded from a database. SQL Handler supports PostgreSQL and SQL Server database systems.\n\nUsing a database for storing EAB credentials is useful in use cases where many `acme2certifier` instances use the same credentials, for example in data centers using load balancing. Instead of maintaining multiple JSON files, credentials can be maintained using a single database instance.\n\nUsing a database is beneficial also when rotating and managing `kid/mac_key` pairs in more complex scenarios, with the addition of an `account` table that can maintain information about the account related to each key.\n\nIt is recommended to use the same [external database](external_database_support.md) for both `acme2certifier` and EAB.\n\n### Database Schema\n\nThe database schema includes two tables, one for actual credentials used by `acme2certifier` and another for managing accounts and credentials. Any number of credentials can be related to a single account. Only the credentials table is actually used by `acme2certifier`.\n\nSchemas for the database systems differ in relation to fields that are used to maintain JSON data. SQL Server has `NVARCHAR` type for that, whereas PostgreSQL has `JSONB`. Schemas also have a status column to indicate if the credentials are active or not (value is 0 or 1).\n\n#### When Using PostgreSQL\n\nCreate a database and then these two tables. See [Usage](#Usage) for entering some data in the tables. Then, [configure acme_srv.cfg](#Activate) with the database credentials that you have.\n\n```sql\nCREATE TABLE account (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(127) NOT NULL,\n    contact VARCHAR(127)\n);\n\nCREATE TABLE credentials (\n    id SERIAL PRIMARY KEY,\n    account_id INT NOT NULL REFERENCES account (id),\n    key_id VARCHAR(63) NOT NULL,\n    profile JSONB,\n    description VARCHAR(255),\n    status SMALLINT NOT NULL\n);\n```\n\n#### When Using SQL Server\n\nCreate a database and then these two tables. See [Usage](#Usage) for entering some data in the tables. Then, [configure acme_srv.cfg](#Activate) with the database credentials that you have.\n\n```sql\nCREATE TABLE account (\n    id INT IDENTITY(1,1) PRIMARY KEY,\n    name NVARCHAR(127) NOT NULL,\n    contact NVARCHAR(127)\n);\n\nCREATE TABLE credentials (\n    id INT IDENTITY(1,1) PRIMARY KEY,\n    account_id INT NOT NULL REFERENCES account (id),\n    key_id NVARCHAR(63) NOT NULL,\n    profile NVARCHAR(MAX),\n    description NVARCHAR(255),\n    status TINYINT NOT NULL\n);\n```\n\n### Usage\n\nIn the simplest scenario, the database will have one account that all the keys are related to.\n\n```sql\nINSERT INTO account (name, contact)\n  VALUES ('myaccount', 'contact@myaccount.com');\n```\n\nThe `profile` column in `credentials` table should contain JSON data in the same format as it is used in JSON Handler.\n\nExample:\n\n```sql\nINSERT INTO credentials (account_id, key_id, description, profile, status)\n  VALUES (\n    (SELECT id FROM account WHERE account.name = 'myaccount'),\n    'keyid_03',\n    'mykey',\n    '{\n        \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\",\n        \"authorization\": {\n            \"prevalidated_domainlist\": [\"www.example.com\"]\n        }\n    }',\n    1\n  );\n```\n\n### Activate Handler\n\nTo activate this handler, configure the `EABhandler` section in `acme_srv.cfg` as follows. For `db_system`, enter either `mssql` or `postgres`.\n\n```ini\n[EABhandler]\neab_profiling: True\neab_handler_file: examples/eab_handler/sql_handler.py\ndb_system: mssql, postgres\ndb_host:\ndb_name:\ndb_user:\ndb_password:\n```\n\n## Keyfile Verification\n\nTo check the consistency of the keyfile, use the `tools/eab_chk.py` utility:\n\n```bash\nusage: eab_chk.py [-h] -c CONFIGFILE [-d] [-v] [-vv] [-k KEYID | -s]\n\neab_chk.py - verify eab keyfile\n\noptions:\n  -h, --help            show this help message and exit\n  -c CONFIGFILE, --configfile CONFIGFILE\n                        configfile\n  -d, --debug           debug mode\n  -v, --verbose         verbose\n  -vv, --veryverbose    show enrollment profile\n  -k KEYID, --keyid KEYID\n                        keyid to filter\n  -s, --summary         summary\n```\n\nExample usage:\n\n```bash\npython /var/www/acme2certifier/tools/eab_chk.py -c /var/www/acme2certifier/acme_srv/acme_srv.cfg -v\n```\n\nExample output:\n\n```bash\nSummary: 4 entries in key_file\nkeyid_00: bWFjXz...Aw\nkeyid_01: bWFjXz...Ax\nkeyid_02: bWFjXz...Ay\nkeyid_03: bWFjXz...Az\n```\n\n## Creating a Custom EAB Handler\n\nCreating a custom EAB handler is straightforward. You need to create a `handler.py` file containing an `EABhandler` class with a `mac_key_get` method to look up the `mac_key` based on a given `kid`.\n\nThe `allowed_domains_check` method is optional and can be used to customize the [`allowed_domainlist_check()` function](https://github.com/grindsa/acme2certifier/blob/master/acme_srv/helper.py#L1641).\n\nThe [skeleton_eab_handler.py](../examples/eab_handler/skeleton_eab_handler.py) provides a template for creating a custom handler.\n\nBelow is an example of the class structure:\n\n```python\nclass EABhandler(object):\n    \"\"\"EAB file handler\"\"\"\n\n    def __init__(self, logger=None):\n        self.logger = logger\n        self.key = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        if not self.key_file:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Close the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\"Load additional configuration parameters from acme_srv.cfg\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n        config_dic = load_config(self.logger, \"EABhandler\")\n        if \"key\" in config_dic[\"EABhandler\"]:\n            self.key = config_dic[\"EABhandler\"][\"key\"]\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def allowed_domains_check(self, csr, value) -> str:\n        \"\"\"Check allowed domains\"\"\"\n        self.logger.debug(\"EABhandler.allowed_domains_check(%s, %s)\", csr, value)\n        error = None  # Return an error message if applicable\n        return error\n\n    def mac_key_get(self, kid=None):\n        \"\"\"Check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get({})\".format(kid))\n        mac_key = None  # Implement logic to look up the mac_key\n        return mac_key\n```\n"
  },
  {
    "path": "docs/eab_profiling.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Enrollment profiling via external account binding -->\n\n# Enrollment profiling via external account binding\n\nStarting with version 0.34 acme2certifier supports the configuration of account specific enrollment configuration. Depending on the handler to be used the feature allows the definition of individual authentication credentials, enrollment profiles or certificate authorities.\n\nCurrently the following ca-handlers have been modified and support this feature:\n\n- [generic ACME](acme_ca.md)\n- [Digicert](digicert.md)\n- [EJBCA](ejbca.md)\n- [Insta ActiveCMS](asa.md)\n- [Insta certifier/NetGuard Certificate manager](certifier.md)\n- [Microsoft Certificate Enrollment Web Services](mscertsrv.md)\n- [Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE) via RPC/DCOM](mswcce.md)\n- [OpenXPKI](openxpki.md)\n- [Vault](vault.md)\n- [XCA](xca.md)\n\nIn case you need support for a different ca-handler feel free to open an [issue](https://github.com/grindsa/acme2certifier/issues/new).\n\n## Configuration\n\nThis feature requires [external account binding](eab.md) to be enabled and a specific EAB-handler to be configured.\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: volume/kid_profiles.json\neab_profiling: True\n```\n\nThe `key_file` allows the specification enrollment parameters per (external) acme-account. Main identifier is the key_id to be used during account registration. Any parameter used in the \\[CAhandler\\] configuration section of a handler can be customized. Starting from acme2certifier v0.38 challenge validation can be disabled for a specific eab-user.\n\nBelow is an example configuration to be used for [Insta Certifier](certifier.md) with some explanation:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"hmac-key\",\n    \"cahandler\": {\n      \"profile_id\": \"profile_1\",\n      \"allowed_domainlist\": [\"*.example.com\", \"*.example.org\", \"*.example.fi\"],\n      \"ca_name\": \"non_default_ca\",\n      \"api_user\": \"non_default_api_user\",\n      \"api_password\": \"api_password\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"hmac-key\",\n    \"cahandler\": {\n      \"profile_id\": [\"profile_1\", \"profile_2\", \"profile_3\"],\n      \"allowed_domainlist\": [\"*.example.fi\", \"*.acme\"]\n    },\n    \"challenge\": {\n      \"challenge_validation_disable\": \"True\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"hmac-key\",\n    \"challenge\": {\n      \"challenge_validation_disable\": \"True\",\n      \"foward_address_check\": \"True\",\n      \"reverse_address_check\": \"True\"\n    }\n  },\n  \"keyid_03\": {\n  \"hmac\": \"hmac-key\",\n  \"authorization\": {\n    \"prevalidated_domainlist\": [\"www.example.fi\", \"*.acme\"]\n  }\n}\n```\n\n- ACME accounts created with keyid \"keyid_00\" will always use profile-id \"profile_1\" and specific api-user credentials for enrollment from certificate authority \"non_default_ca\". Further, the SANs/Common Names to be used in enrollment requests are restricted to the domains \"example.com\", \"example.org\" and \"example.fi\".\n- ACME accounts created with keyid \"keyid_01\" and can specify 3 different profile_ids by using the [header_info feature](header_info.md). Enrollment requests having other profile_ids will be rejected. In case no profile_id get specified the first profile_id in the list (\"profile_1\") will be used. SAN/CNs to be used are restricted to \"example.fi\" and \".local\" All other enrollment parameters will be taken from acme_srv.cfg. Furthermore the challenge validation got disabled for this user which means that acme2certifier will accept any CN/SAN matching the pattern \"*.example.fi\" or \"*.acme\".\n- ACME accounts created with keyid \"keyid_02\" do not have any special enrollment configuation as al parameters will be taken from the \\[CAhandler\\] section in ´acme_srv.cfg´. Furthermore, challenge validadaion got disabled and both forward and reverse address checking gets activated.\n- ACME accounts created wiht keyid \"keyid_03\" can use the [pre-validaton domainlist](prevalidated_domainlist.md) feature to enroll certificates for \"www.example.fi\" and \"\\*.acme\" without challenge-validation\n\nStarting from v0.36 acme2certifier does support profile configuration in yaml format. Below a configuration example providing the same level of functionality as the above JSON configuration\n\n```yaml\n---\nkeyid_00:\n  hmac: \"hmac-key\"\n  cahandler:\n    profile_id: \"profile_1\"\n    allowed_domainlist:\n    - \"*.example.com\"\n    - \"*.example.org\"\n    - \"*.example.fi\"\n    ca_name: \"non_default_ca\"\n    api_user: \"non_default_api_user\"\n    api_password: \"api_password\"\n\nkeyid_01:\n  hmac: \"hmac-key\"\n  cahandler:\n    profile_id:\n    - \"profile_1\"\n    - \"profile_2\"\n    - \"profile_3\"\n    allowed_domainlist:\n    - \"*.example.fi\"\n    - \"*.acme\"\n  challenge:\n    challenge_validation_disable\": True\n\nkeyid_02:\n  hmac: \"hmac-key\"\n  challenge:\n    challenge_validation_disable\": True\n    forward_address_check: True\n    reverse_address_check: True\n\nkeyid_03:\n  hmac: \"hmac-key\"\n  authorization:\n    prevalidated_domainlist:\n    - \"www.example.fi\"\n    - \"*.acme\"\n```\n\n## Subject Profiling\n\nStarting from v0.36 the eab-profiling feature can be used to check and white-list the certificate subject DN.\n\nAttribute names must follow [RFC3039](https://www.rfc-editor.org/rfc/rfc3039.html#section-3.1.2); every RDN can be white-listed as:\n\n- string - attribute in CSR DN must match this value\n- list - attribute in CSR DN must match one of the list entries\n- \"\\*\" - any value matches as long as the attribute is present\n\nThe below example configuration will only allow CSR matching the following criteria:\n\n- serial number can be of any value but must be included\n- organizationalUnitName must be one of \"acme1\" or \"acme2\"\n- organizationName must be \"acme corp\"\n- countryName must be \"AC\"\n- additional CSR DN attributes such as localityName or stateOrProvinceName are not allowed\n\n```json\n...\n{\n  \"keyid_00\": {\n    \"hmac\": \"hmac-key\",\n    \"cahandler\": {\n      \"template_name\": [\"template\", \"acme\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"subject\": {\n        \"serialNumber\": \"*\",\n        \"organizationName\": \"acme corp\",\n        \"organizationalUnitName\": [\"acme1\", \"acme2\"],\n        \"countryName\": \"AC\"\n       }\n    }\n  }\n}\n...\n```\n\n## Profile verification\n\nThe key file can be checked for consistency by using the `tools/eab_chk.py` utility.\n\n```bash\n py /var/www/acme2certifier/tools/eab_chk.py --help\n```\n\n```bash\nusage: eab_chk.py [-h] -c CONFIGFILE [-d] [-v] [-vv] [-k KEYID | -s]\n\neab_chk.py - verify eab keyfile\n\noptions:\n  -h, --help            show this help message and exit\n  -c CONFIGFILE, --configfile CONFIGFILE\n                        configfile\n  -d, --debug           debug mode\n  -v, --verbose         verbose\n  -vv, --veryverbose    show enrollment profile\n  -k KEYID, --keyid KEYID\n                        keyid to filter\n  -s, --summary         summary\n```\n\nBelow is an example output by using the above mentioned keyfile\n\n- show a summary only\n\n```bash\npy /var/www/acme2certifier/tools/eab_chk.py  -c /var/www/acme2certifier/acme_srv/acme_srv.cfg\n```\n\n```bash\nSummary: 4 entries in kid_file\n```\n\n- show keyids and hmac\n\n```bash\n py /var/www/acme2certifier/tools/eab_chk.py  -c /var/www/acme2certifier/acme_srv/acme_srv.cfg -v\n```\n\n```bash\nSummary: 4 entries in kid_file\nkeyid_00: V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\nkeyid_01: YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\nkeyid_02: dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\nkeyid_03: YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\n```\n\n- show profiles\n\n```bash\npy /var/www/acme2certifier/tools/eab_chk.py  -c /var/www/acme2certifier/acme_srv/acme_srv.cfg -vv\n```\n\n```bash\nSummary: 4 entries in kid_file\nkeyid_00:\n  cahandler:\n    allowed_domainlist:\n    - www.example.com\n    - www.example.org\n    - '*.example.fi'\n    - '*.bar.local'\n    profile_id:\n    - '101'\n    - '102'\n    profile_name:\n    - ACME_2\n    - ACME\n    template_name:\n    - TLS_Server\n    - acme\n  hmac: V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\nkeyid_01:\n  cahandler:\n    allowed_domainlist:\n    - '*.example.fi'\n    - '*.acme'\n    - '*.bar.local'\n    profile_id: '101'\n    profile_name: ACME_2\n    template_name: TLS_Server\n  hmac: YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\nkeyid_02:\n  cahandler:\n    ca_name: RSA Root CA\n  hmac: dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\nkeyid_03:\n  hmac: YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\n```\n\n- filter output to a single keyid\n\n```bash\npy /var/www/acme2certifier/tools/eab_chk.py  -c /var/www/acme2certifier/acme_srv/acme_srv.cfg -k keyid_01\n```\n\n```bash\nSummary: 1 entries in kid_file\nkeyid_01:\n  cahandler:\n    allowed_domainlist:\n    - '*.example.fi'\n    - '*.acme'\n    - '*.bar.local'\n    profile_id: '101'\n    profile_name: ACME_2\n    template_name: TLS_Server\n  hmac: YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\n```\n"
  },
  {
    "path": "docs/ejbca.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler for EJBCA -->\n\n# Connecting to Keyfactor's EJBCA\n\nThis handler can be used to enroll certificates from the [Open Source version of Keyfactor's EJBCA](https://www.ejbca.org) as ACME support is only available in the Enterprise version.\n\n## Prerequisites\n\n- [EJBCA](https://www.ejbca.org) needs to have the RESTv1-service enabled\n- you'll need:\n  - a [client certificate and key in p12](https://docs.keyfactor.com/ejbca/latest/authentication-methods) format to authenticate towards the REST service\n  - the name of the CA issuing the certificates from EJBA admin UI\n  - a username and enrolment code\n  - a [certificate profile name](https://docs.keyfactor.com/ejbca/latest/certificate-profiles-overview)\n  - an [end-entity profile name](https://docs.keyfactor.com/ejbca/latest/end-entity-profiles-overview)\n\nThe handler requires the installation of the python [requests_pkcs12](https://github.com/m-click/requests_pkcs12) module. The module can be installed via [pypi](https://pypi.org/project/requests-pkcs12/), RPMs for RH8 can be found in my [rpm-repo](https://github.com/grindsa/sbom/tree/main/rpm-repo/RPMs)\n\n## Configuration\n\n- modify the server configuration (`acme_srv.cfg`) and add the following parameters\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/ejbca_ca_handler.py\napi_host: https://<fqdn or ip>:8443\ncert_file: <filename>\ncert_passphrase: <passphrase>\nca_bundle: <filename>\ncert_profile_name: <name>\nee_profile_name: <name>\nusername: <name>\nusername_append_cn: <True|False>\nenrollment_code: <value>\nca_name: <name>\nrequest_timeout: <seconds>\n```\n\n- api_host - URL of the EJBCA-Rest service\n- cert_file - certificate and key in pkcs#12 format to authenticate towards EJBCA-Rest service\n- cert_passphrase - passphrase to access the pkcs#12 container\n- cert_passphrase_variable - *optional* - name of the environment variable containing the cert_passphrase (a configured `cert_passphrase` parameter in acme_srv.cfg takes precedence)\n- ca_bundle - optional - ca certificate chain in pem format needed to validate the EJBCA server certificate - can be True/False or a filename (default: True)\n- username - EJBCA username\n- username_variable - *optional* - name of the environment variable containing the EJBCA username (a configured `username` parameter in acme_srv.cfg takes precedence)\n- username_append_cn - *optional* - add common-name (or 1st SAN) to EJBCA username to allow a better differenciation in the EJBCA-UI\n- enrollment_code - enrollment code\n- enrollment_code_variable - *optional* - name of the environment variable containing the enrollment_code for the EJBCA user (a configured `enrollment_code` parameter in acme_srv.cfg takes precedence)\n- cert_profile_name - name of the certificate profile\n- ee_profile_name - name of the end entity profile\n- ca_name - name of the CA used to enroll certificates\n- allowed_domainlist - optional - list of domain-names allowed for enrollment in JSON format, for example: \\[\"bar.local$, bar.foo.local\\] (default: \\[\\])\n- enrollment_config_log - optional - log enrollment parameters (default False)\n- enrollment_config_log_skip_list - optional - list of enrollment parameters not to be logged in JSON format, for example: \\[ \"parameter1\", \"parameter2\" \\] (default: \\[\\])\n- request_timeout - optional - requests timeout in seconds for requests (default: 5s)\n\nYou can test the connection by running the following curl command against your EJBCA server.\n\n```bash\nroot@rlh:~#  curl https://<api-host>/ejbca/ejbca-rest-api/v1/certificate/status --cert-type P12 --cert <cert_file>:<cert_passphrase> --cacert <ca_bundle>\n```\n\nThe response to this call will show a dictionary containing status und version number of the server.\n\n```json\n{\n  \"status\":\"OK\",\n  \"version\":\"1.0\",\n  \"revision\":\"EJBCA 7.11.0 Community (8d14e27cda0b32eba35a1fd1423f8e6a31d1ed8e)\"\n}\n```\n\nUse your favorite acme client for certificate enrollment. A list of clients used in our regression can be found in the [disclaimer section of our README file](../README.md)\n\n## Passing a profile_id from client to server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `cert_profile_name` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}\n```\n\nOnce enabled, a client can specify the cert_profile_name to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile profile2\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md) allowing an ACME client to specify a certificate profile to be used during certificate enrollment. This feature is disabled by default and must be activated in `acme_srv.cfg` as shown below\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe ACME client can then specify the profileID as part of its user-agent string.\n\nExample for acme.sh:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent cert_profile_name=acme_clt --debug 3 --output-insecure\n```\n\nExample for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent cert_profile_name=acme_clt -d <fqdn> --http run\n```\n\n## eab profiling\n\nThis handler can use the [eab profiling feature](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activatedd in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nBelow is an example key file used during regression testing:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"cert_profile_name\": [\"acmeca2\", \"acmeca1\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"]\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"cert_profile_name\": \"acmeca2\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"ca_name\": \"acmeca\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/entrust.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler for Entrust ECS Enterprise -->\n\n# Connecting to Entrust ECS Enterprise\n\nThis handler can be used to enroll certificates from Entrust ECS Enterprise API.\n\n## Prerequisites\n\n- you'll need:\n  - Username and Password for HTTP-BASIC authentication\n  - if configured - a client certificate for mutual TLS authentication towards the Entrust REST API\n  - a pre-validated Organization name\n\n## Configuration\n\n- modify the server configuration (`acme_srv.cfg`) and add the first three of the below mentioned parameters\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/entrust_ca_handler.py\nusername: <Username>\npassword: <Password>\ncert_type: <certificate type>\norganization_name: <organization name>\n\nclient_cert: <client file>\ncert_passphrase: <pkcs#12 passphrase>\ncert_validity_days: <certificate validity>\nallowed_domainlist: <allowed_domainlist>\nrequest_timeout: <seconds>\neab_profiling: <True|False>\n```\n\n- username - required - username access the API\n- password - required - password to access the PI\n- organization_name - required - Organization name as specified in DigiCert CertCentral\n- client_cert - optional - client certificate to access the API (to be stored in either pem or pkcs#12 format)\n- client_key - optional - client private key to access the API (must be stored in pem format)\n- client_passphrase - passphrase to access the client_cert (if stored in PKCS#12 format)\n- cert_type - optional - certificate type to be issued. (default: STANDARD_SSL)\n- cert_validity_days - certificate validity in days (default: 365)\n- allowed_domainlist: list of domain-names allowed for enrollment in JSON format (example: \\[\"bar.local$, bar.foo.local\\])\n- request_timeout - optional - request timeout in seconds for requests (default: 5s)\n- allowed_domainlist - optional - list of domain-names allowed for enrollment in JSON format example: \\[\"bar.local$, bar.foo.local\\] (default: \\[\\])\n- eab_profiling - optional - [activate EAB profiling](eab_profiling.md) (default: False)\n- enrollment_config_log - optional - log enrollment parameters (default False)\n- enrollment_config_log_skip_list - optional - list enrollment parameters not to be logged in json format example: \\[ \"parameter1\", \"parameter2\" \\] (default: \\[\\])\n\nUse your favorite acme client for certificate enrollment. A list of clients used in our regression can be found in the [disclaimer section of our README file](../README.md)\n\n## Passing a cert_type from client to server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `cert_type` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"STANDARD_SSL\": \"http://foo.bar/STANDARD_SSL\", \"ADVANTAGE_SSL\": \"http://foo.bar/ADVANTAGE_SSL\"}\n```\n\nOnce enabled, a client can specify the cert_type to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile ADVANTAGE_SSL\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md) allowing an acme-client to specify a certificate type to be used during certificate enrollment. This feature is disabled by default and must be activated in `acme_srv.cfg` as shown below\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe acme-client can then specify the cert_type as part of its user-agent string.\n\nExample for acme.sh:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent cert_type=ADVANTAGE_SSL --debug 3 --output-insecure\n```\n\nExample for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent cert_type=ADVANTAGE_SSL -d <fqdn> --http run\n```\n\n## eab profiling\n\nThis handler can use the [EAB profiling feature](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activatedd in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\n\n[CAhandler]\neab_profiling: True\n```\n\nbelow an example key file used during regression testing:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"cert_type\": [\"ADVANTAGE_SSL\", \"STANDARD_PLUS_SSL\", \"WILDCARD_SSL\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"organization_name\": \"acme2certifier\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"cert_type\": \"ADVANTAGE_SSL\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/est.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title CA handler using EST protocol -->\n\n# Generic EST protocol handler\n\nThe EST protocol handler is not bound to a specific CA server and implements the ['cacerts'](https://tools.ietf.org/html/rfc7030#section-2.1) and ['simpleenroll'](https://tools.ietf.org/html/rfc7030#section-2.2) calls as defined in [RFC7030](https://tools.ietf.org/html/rfc7030).\n\nWhen using the handler please be aware of the following limitations:\n\n- Authentication towards CA server is limited to ClientAuth as described in [RFC7030 section 3.3.2](https://tools.ietf.org/html/rfc7030#section-3.3.2) and HTTP-BASIC authentication as described in [RFC7030 section 3.2.3](https://tools.ietf.org/html/rfc7030#section-3.2.3)\n- Revocation operations are not supported\n\nThe handler has been tested with the following EST implementation:\n\n- [Insta Certifier](https://www.insta.fi/en/services/cyber-security/insta-certifier)\n- EST reference implementation from [Cisco](http://testrfc7030.com/)\n\nWhen using the Cisco test server make sure that the csr generated by your ACME client has a valid common-name. So enrollment by using [Certbot](https://certbot.eff.org/) is unfortunately not possible.\n\n## Pre-requisites\n\n- Certificate and key (in PEM format) used to authenticate acme2certifier towards EST server.\n- CA certificate(s) in PEM format allowing to validate the certificate presented by the EST server. The CA certificates must be bundled into a single chain file as described in [RFC5246 section 7.4.2](https://tools.ietf.org/html/rfc5246#section-7.4.2)\n\n## Installation and Configuration\n\n- modify the server configuration (/acme_srv/acme_srv.cfg) and add the following parameters\n\n```config\n[CAhandler]\nhandler_file: examples/ca_handler/est_ca_handler.py\nest_host: https://<ip>:<port>\nest_client_key: <filename>\nest_client_cert: <filename>\nest_user: <user_name>\nest_password: <password>\nca_bundle: <filename>\n```\n\n- est_host - URL of the EST server service\n- est_host_variable - *optional* - name of the environment variable storing the est server url (a configured `est_host` parameter in acme_srv.cfg takes precedence)\n- est_client_cert - Certificate used for TLS client-auth (in either PEM or PKCS#12 format)\n- _either_: est_client_key - Private key of the certificate used for TLS client-auth (in pem-format)\n- _or_: cert_passphrase - passphrase to access the pkcs#12 container\n- _or_: cert_passphrase_variable - *optional* - name of the environment variable containing the cert_passphrase (a configured `cert_passphrase` parameter in acme_srv.cfg takes precedence)\n- est_user - username for HTTP Basic Authentication\n- est_user_variable - *optional* - name of the environment variable specifying the username for HTTP basic authentication (a configured `est_user` parameter in acme_srv.cfg takes precedence)\n- est_password - password for HTTP Basic Authentication\n- est_password_variable - *optional* - name of the environment variable specifying the user password for HTTP basic authentication (a configured `est_password` parameter in acme_srv.cfg takes precedence)\n- ca_bundle - CA certificate bundle needed to validate the EST server certificate (acme_srv/est/ca_bundle.pem). Setting to False disables the certificate check\n- allowed_domainlist - optional - list of domain-names allowed for enrollment in JSON format, for example: \\[\"bar.local$, bar.foo.local\\] (default: \\[\\])\n\nImportant: TLS Client Authentication and HTTP basic Authentication cannot be combined with each other\n\nBelow is the CA bundle needed to interoperate with EST reference implementation from [Cisco](http://testrfc7030.com/)\n\n```pem\nsubject=CN = estExampleCA\n\nissuer=CN = estExampleCA\n\n-----BEGIN CERTIFICATE-----\nMIIBUjCB+qADAgECAgkAsOsMO552gHQwCgYIKoZIzj0EAwIwFzEVMBMGA1UEAxMM\nZXN0RXhhbXBsZUNBMB4XDTE5MDgwOTIwMjUzOFoXDTI5MDgwNjIwMjUzOFowFzEV\nMBMGA1UEAxMMZXN0RXhhbXBsZUNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\ne/4TlZtkyUP7v6F8GHdJLzjQvwahFDBj0L/oPfxf00oDHya5wsU2wT0cV7L70hPD\n1n4dxhG/1JYX2UK10zflqKMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU2f8O\ncSG4J8B3LPU203cyUF2DQCEwCgYIKoZIzj0EAwIDRwAwRAIgTgMXKl86lcQr3mTo\n2uXbSZt8had163ft+9LBCqoxHiICIAfzhrTBBKSUxZQDeGIahr4OLQlS7GeSNGK1\ney5tEG+Z\n-----END CERTIFICATE-----\n```\n"
  },
  {
    "path": "docs/external_database_support.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: Support for External Databases -->\n\n# Support for External Databases\n\nAcme2certifier supports external databases by using the [Django Python framework](https://www.djangoproject.com/). The default SQLite backend is not designed to handle concurrent write access, which can easily occur in an environment with a high transaction frequency.\n\nAll [databases supported by Django](https://docs.djangoproject.com/en/5.0/ref/databases/) should work in theory; MariaDB and PostgreSQL will be tested during [release regression](https://github.com/grindsa/acme2certifier/blob/master/.github/workflows/django_tests..yml).\n\nThis guide is written for **Ubuntu 24.04**; however, adapting it to other Linux distributions should not be difficult.\n\n## Preparation\n\n### When Using MariaDB\n\nThe steps below assume that MariaDB is already installed and running on your system.\n\n- Open the MySQL command-line client:\n\n```bash\nsudo mysql -u  root\n```\n\n- create the acme2certifier database and database user\n\n```SQL\nCREATE DATABASE acme2certifier CHARACTER SET UTF8;\nGRANT ALL PRIVILEGES ON acme2certifier.* TO 'acme2certifier'@'%' IDENTIFIED BY 'a2cpasswd';\nFLUSH PRIVILEGES;\n```\n\n- Install missing Python modules:\n\n```bash\napt-get install python3-django python3-mysqldb python3-pymysql\n```\n\n### When using PostgreSQL\n\nIt is assumed that PostgreSQL is already installed and running.\n\n- Open the PostgreSQL command-line client:\n\n```bash\nsudo psql -U postgres\n```\n\n- Create the acme2certifier database and database user:\n\n```SQL\nCREATE DATABASE acme2certifier;\nCREATE USER acme2certifier WITH PASSWORD 'a2cpasswd';\nALTER ROLE acme2certifier SET client_encoding TO 'utf8';\nALTER ROLE acme2certifier SET default_transaction_isolation TO 'read committed';\nALTER ROLE acme2certifier SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE acme2certifier TO acme2certifier;\nGRANT ALL ON schema public TO acme2certifier;\nGRANT USAGE ON schema public TO acme2certifier;\nGRANT postgres TO acme2certifier;\n```\n\n- Install missing python modules\n\n```bash\nsudo apt-get install python3-django python3-psycopg2\n```\n\n### When using SQL Server\n\n_SQL Server support has not been tested in the [release regression](https://github.com/grindsa/acme2certifier/actions/workflows/django_tests..yml) to the same extent as the other two databases._\n\nNote that this part of the guide is written for **Red Hat Enterprise Linux 9**.\n\nIt is assumed that SQL Server is already installed and running.\n\nOpen SQL Server Management Studio.\n\n- Create the acme2certifier database and database user:\n\n```SQL\nCREATE DATABASE acme2certifier;\nCREATE LOGIN acme2certifier WITH PASSWORD = 'a2c+passwd';\nCREATE USER acme2certifier FOR LOGIN acme2certifier;\n```\n\n- From Object Explorer, open acme2certifier, Security, Logins, acme2certifier Properties. Then, from User Mapping, map the user to the database and give necessary roles. From Server Roles, give public and sysadmin roles. In essence, grant all access to the database for the acme2certifier user.\n\n- Install missing python modules\n\n```bash\npip install mssql-django pyodbc\nsudo dnf install unixODBC\n```\n\n- Follow [these instructions](https://learn.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15&tabs=redhat18-install%2Credhat17-install%2Cdebian8-install%2Credhat7-13-install%2Crhel7-offline#17) to install Microsoft ODBC 17.\n\n## Install and Configure acme2certifier\n\n- Download the [latest deb package](https://github.com/grindsa/acme2certifier/releases)\n- Install the package locally\n\n```bash\nsudo apt-get install -y ./acme2certifier_<version>-1_all.deb\n```\n\n- Copy and activate Apache2 configuration file\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-available/acme2certifier.conf\nsudo a2ensite acme2certifier\n```\n\n- Copy and activate the Apache2 SSL configuration file (optional):\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\nsudo a2ensite acme2certifier_ssl\n```\n\n- Disable the default sites:\n\n```bash\nsudo a2dissite 000-default.conf\nsudo a2dissite default-ssl\n```\n\n- Copy the Django handler and the Django directory structure:\n\n```bash\nsudo cp /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\nsudo cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\n```\n\n- Enable and start the Apache2 service:\n\n```bash\nsudo systemctl enable apache2.service\nsudo systemctl start apache2.service\n```\n\n- Generate a new Django secret key and note it down:\n\n```bash\npython3 -c \"import secrets; print(secrets.token_urlsafe(50))\"\n```\n\n- Modify `/var/www/acme2certifier/acme2certifier/settings.py` and:\n  - Insert the secret-key created in the previous step\n  - Update the 'ALLOWED_HOSTS'- section with both ip-address and fqdn of the node\n  - Configure a connection to mariadb as shown below\n\n```python\nSECRET_KEY = \"+%*lei)yj9b841=2d5(u)a&7*uwi@l99$(*&ong@g*p1%q)g$e\"\nALLOWED_HOSTS = [\"192.168.14.132\", \"ub2204-c1.bar.local\"]\n(...)\n```\n\n### Connecting to MariaDB\n\n- Modify `/var/www/acme2certifier/acme2certifier/settings.py` and configure your database connection as below:\n\n```python\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"ub2204-c1\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n```\n\n### Connecting to PostGres\n\n- Modify `/var/www/acme2certifier/acme2certifier/settings.py` and configure your database connection as below:\n\n```python\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql_psycopg2\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2cpasswd\",\n        \"HOST\": \"postgresdbsrv\",\n        \"PORT\": \"\",\n    }\n}\n```\n\n### Connecting to SQL Server\n\n- Modify `/var/www/acme2certifier/acme2certifier/settings.py` and configure your database connection as below:\n\n```python\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"mssql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": \"a2c+passwd\",\n        \"HOST\": \"sqlserverdbsrv,1433\",\n        \"PORT\": \"\",\n        \"OPTIONS\": {\"driver\": \"ODBC Driver 17 for SQL Server\"},\n    }\n}\n```\n\n- You may also need to disable some SELinux settings for Apache, depending on your server configuration.\n\n## Finalize acme2cerifier configuration\n\n- Create a Django migration set, apply the migrations, and load fixtures: Modify the [configuration file](acme_srv.md) `/var/www/acme2certifier/volume/acme_srv.cfg`according to your needs. If your CA handler needs runtime information (configuration files, keys, certificate bundles, etc.) to be shared between the nodes, ensure they are loaded from `/var/www/acme2certifier/volume`. Below is an example for the `[CAhandler]` section of the openssl-handler I use during my tests:\n\n```cfg\n[CAhandler]\nhandler_file: /var/www/acme2certifier/examples/ca_handler/openssl_ca_handler.py\nca_cert_chain_list: [\"/var/www/acme2certifier/volume/root-ca-cert.pem\"]\nissuing_ca_key: /var/www/acme2certifier/volume/ca/sub-ca-key.pk8\nissuing_ca_key_passphrase_variable: OPENSSL_PASSPHRASE\nissuing_ca_cert: /var/www/acme2certifier/volume/ca/sub-ca-cert.pem\nissuing_ca_crl: /var/www/acme2certifier/volume/ca/sub-ca-crl.pem\ncert_validity_days: 30\ncert_validity_adjust: True\ncert_save_path: /var/www/acme2certifier/volume/ca/certs\nsave_cert_as_hex: True\ncn_enforce: True\n```\n\n- Create a Django migration set, apply the migrations, and load fixtures:\n\n```bash\ncd /var/www/acme2certifier\nsudo python3 manage.py makemigrations\nsudo python3 manage.py migrate\nsudo python3 manage.py loaddata acme_srv/fixture/status.yaml\n```\n\n- Run the Django update script:\n\n```bash\nsudo python3 /var/www/acme2certifier/tools/django_update.py\n```\n\n- Restart the apache2 service\n\n```bash\nsudo systemctl restart apache2.service\n```\n\n- Test the server by accessing the directory resource\n\n```bash\ncurl http://ub2204-c1.bar.local/directory\n```\n\n```bash\n{\"newAccount\": \"http://ub2204-c1.bar.local/acme_srv/newaccount\", \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\", \"keyChange\": \"http://ub2204-c1.bar.local/acme_srv/key-change\", \"newNonce\": \"http://ub2204-c1.bar.local/acme_srv/newnonce\", \"meta\": {\"home\": \"https://github.com/grindsa/acme2certifier\", \"author\": \"grindsa <grindelsack@gmail.com>\"}, \"newOrder\": \"http://ub2204-c1.bar.local/acme_srv/neworders\", \"revokeCert\": \"http://ub2204-c1.bar.local/acme_srv/revokecert\"}\n```\n\n## Test enrollment\n\n- Try to enroll certificates by using your favorite ACME client. I am using [lego](https://github.com/go-acme/lego).\n\n```bash\n docker run -i -p 80:80 -v $PWD/lego:/.lego/ --rm --name lego --network acme goacme/lego --tls-skip-verify -s https://ub2204-c1.bar.local -a --email \"lego@example.com\" -d lego01.bar.local --http run\n```\n"
  },
  {
    "path": "docs/header_info.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: Pass Information from ACME Client to CA Handler -->\n\n# Pass Information from ACME Client to CA Handler\n\nSince version 0.30, `acme2certifier` allows passing information to the CA handler. To maintain compatibility with [RFC8555](https://datatracker.ietf.org/doc/html/rfc8555), ACME clients need to insert this information as attributes into the HTTP header, which is part of the order-finalization message.\n\nThe header attributes, including the payload, must be specified in `acme_srv.cfg`:\n\n```config\n[Order]\nheader_info_list: [\"HTTP_USER_AGENT\", \"CONTENT_TYPE\", \"REMOTE_ADDR\"]\n```\n\nThe headers will be added to the `header_info` column of the certificates table. The CA handler can retrieve this information using the `header_info_get()` function from `helper.py` as serialized JSON.\n\n```python\nclass CAHandler(object):\n    ...\n\n    def enroll(self, csr):\n        \"\"\"Enroll certificate\"\"\"\n        self.logger.debug(\"CAHandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_identifier = None\n\n        # Lookup HTTP header information from request\n        qset = header_info_get(self.logger, csr=csr)\n        if qset:\n            self.logger.info(\"Header info: {0}\".format(qset[-1][\"header_info\"]))\n            # Perform additional processing with the header information...\n        ...\n        self.logger.debug(\"Certificate.enroll() ended\")\n\n        return (error, cert_bundle, cert_raw, poll_identifier)\n```\n\nThe output from the above configuration example would be:\n\n```log\n2023-11-03 16:52:14 - acme2certifier - INFO - Header info: {\"HTTP_USER_AGENT\": \"CertbotACMEClient/1.21.0 (certbot; Ubuntu 22.04.3 LTS) Authenticator/standalone Installer/None (certonly; flags: ) Py/3.10.12\", \"CONTENT_TYPE\": \"application/jose+json\", \"REMOTE_ADDR\": \"192.168.14.131\"}\n```\n"
  },
  {
    "path": "docs/hooks.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: Hooks -->\n\n# Hooks\n\n`acme2certifier` allows for the specification of pre- and post-enrollment hooks. Hooks are disabled by default and must be activated in `acme_srv.cfg` by specifying a file containing the required `Hooks` class and methods.\n\n```config\n[Hooks]\nhooks_file: examples/hooks/skeleton_hooks.py\n```\n\n## How to Create Your Own Hooks\n\nCreating your own hook handler is straightforward. All you need to do is create a `handler.py` file with a `Hooks` class containing the following methods:\n\n- `pre_hook` - Executed before certificate enrollment.\n- `post_hook` - Executed after certificate enrollment, regardless of the result.\n- `success_hook` - Executed in case of a successful certificate enrollment; this runs *before* the `post_hook`.\n\nThe [skeleton_hooks.py](../examples/hooks/skeleton_hooks.py) file contains a template that can be used to create a customized handler.\n\nFurther there is an [Email Hook](../examples/hooks/email_hooks.py) sending emails in case of successful or failed certificate enrollments.\n\nThe following code describes the different input parameters provided by `acme2certifier`, as well as the expected return values:\n\n```python\nclass Hooks:\n    \"\"\"Hooks file handler\"\"\"\n\n    def __init__(self, logger) -> None:\n        self.logger = logger\n\n    def pre_hook(self, certificate_name, order_name, csr) -> None:\n        \"\"\"Run before obtaining any certificates\"\"\"\n        self.logger.debug(\"Hook.pre_hook()\")\n\n    def post_hook(self, certificate_name, order_name, csr, error) -> None:\n        \"\"\"Run after *attempting* to obtain/renew certificates\"\"\"\n        self.logger.debug(\"Hook.post_hook()\")\n\n    def success_hook(\n        self,\n        certificate_name,\n        order_name,\n        csr,\n        certificate,\n        certificate_raw,\n        poll_identifier,\n    ) -> None:\n        \"\"\"Run after each successful certificate enrollment/renewal\"\"\"\n        self.logger.debug(\"Hook.success_hook()\")\n```\n\n### Input Parameters\n\n- `self.logger` - Reference to a logging object.\n- `certificate` - Certificate in `application/pem-certificate-chain` format.\n- `certificate_name` - Name of the certificate resource in `acme2certifier`.\n- `certificate_raw` - Certificate in base64-encoded binary format.\n- `csr` - Certificate Signing Request in base64-encoded binary format.\n- `error` - Error message in case of certificate enrollment failure.\n- `order_name` - Name of the order resource in `acme2certifier`.\n\nThe different methods must not return any data. Exceptions during hook execution are handled by `acme2certifier`, as described below.\n\n## Handling Exceptions\n\nBy default, certificate enrollment/renewal is aborted (and `acme2certifier` returns an error code to the client) if either the `pre_hook` or `success_hook` fails. In such cases, later hooks are also not executed:\n\n- If the `pre_hook` fails, neither the `success_hook` nor `post_hook` is executed.\n- If the `success_hook` fails, the `post_hook` is not executed.\n\nIf the `post_hook` throws an exception, the error is logged, but it has no effect on the enrollment/renewal process. That is, the process completes successfully as long as no other errors occur.\n\nThis behavior can be controlled through the configuration options `allow_pre_hook_failure`, `allow_post_hook_failure`, and `allow_success_hook_failure`. See [the configuration table](acme_srv.md#configuration-options-for-acme2certifier) for more details.\n"
  },
  {
    "path": "docs/housekeeping.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title: Reporting and Housekeeping -->\n\n# Reporting and Housekeeping\n\nThe `Housekeeping` class contains several methods for internal reporting and database maintenance.\n\nTo use it, you need to import the class into your script:\n\n```python\nfrom acme.housekeeping import Housekeeping\n```\n\nThen, create a corresponding context handler:\n\n```python\nwith Housekeeping(LOGGER, DEBUG) as housekeeping:\n```\n\n- `LOGGER` is an instance of a `logging` object. It is recommended to use the `logger_setup()` method from `acme.helper` to create it:\n\n```python\nfrom acme.helper import logger_setup\n\nLOGGER = logger_setup()\n```\n\n- `DEBUG` (True/False) - Enables or disables debug mode.\n\n## Reporting\n\nThere are two methods for generating reports. Both methods return the report as a dictionary. Optionally, the reports can be saved to a file. The report name and format can be specified as shown below.\n\n- `accountreport_get(report_format, report_name, nested)`: Generates a report containing a list of accounts along with corresponding orders, authorizations, and challenges.\n\n  - `report_format`: Optional - `csv`/`json` - Specifies the report format (default: `csv`).\n  - `nested`: Optional - `False`/`True` - Creates a nested JSON report structure (default: `False`).\n  - `report_name`: Optional - Specifies the report file name (default: `account_report_YY-MM-DD-HHMM.<report_format>`).\n\n- `certificatereport_get(report_format, report_name)`: Generates a report containing a list of certificates along with corresponding accounts and orders.\n\n  - `report_format`: Optional - `csv`/`json` - Specifies the report format (default: `csv`).\n  - `report_name`: Optional - Specifies the report file name.\n\nExample reports and the database used to generate the reports can be found in the [examples/reports](../examples/reports) directory.\n\n## Housekeeping\n\nThere are several methods for internal database maintenance.\n\n- `certificate_cleanup(uts, purge, report_format, report_name)`: Identifies expired certificates from the `certificate` table. This method can either remove the X.509 object to reduce database size or delete the entire dataset. Optionally, a report of the selected certificates can be saved to a file.\n\n  - `uts`: Optional - Unix timestamp to compare certificates against. If not specified, the current Unix timestamp will be used.\n  - `purge`: Optional - `True`/`False`. If set to `True`, the entry is removed from the `certificate` table. If `False`, the X.509 object is overwritten with the string `\"removed by acme2certifier\"`. **Use this option carefully and back up `acme_srv.db` before cleaning your database.**\n  - `report_format`: Optional - `csv`/`json` - Specifies the report format (default: `csv`).\n  - `report_name`: Optional - Specifies the report file name.\n\n- `order_invalidate(uts, report_format, report_name)`: Sets all expired orders to the \"invalid\" state. This method must be run regularly if the `expiry_check_disable` parameter is enabled in the `[orders]` section of `acme_srv.cfg`.\n\n  - `uts`: Optional - Unix timestamp for order comparison. If not specified, the current Unix timestamp will be used.\n  - `report_format`: Optional - `csv`/`json` - Specifies the report format (default: `csv`).\n  - `report_name`: Optional - Specifies the report file name.\n\n- `authorization_invalidate(uts, report_format, report_name)`: Sets all expired authorizations to the \"invalid\" state. This method must be run regularly if the `expiry_check_disable` parameter is enabled in the `[authorization]` section of `acme_srv.cfg`.\n\n  - `uts`: Optional - Unix timestamp for authorization comparison. If not specified, the current Unix timestamp will be used.\n  - `report_format`: Optional - `csv`/`json` - Specifies the report format (default: `csv`).\n  - `report_name`: Optional - Specifies the report file name.\n"
  },
  {
    "path": "docs/install_apache2_wsgi.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title: Installation on Apache2 Running on Ubuntu 22.04 -->\n\n# Installation on Apache2 Running on Ubuntu 22.04\n\nA [ready-made shell script](../examples/install_scripts/a2c-ubuntu22-apache2.sh) performing the tasks below can be found in the `examples/install_scripts` directory.\n\n## 1. Install Apache2 and the Corresponding WSGI Module\n\n```bash\nsudo apt-get install -y apache2 libapache2-mod-wsgi-py3 python3-pip apache2-data curl krb5-user libgssapi-krb5-2 libkrb5-3 python3-gssapi\n```\n\n## 2. Check if the WSGI Module is Activated in Your Apache Configuration\n\n```bash\nsudo apache2ctl -M | grep -i wsgi\n wsgi_module (shared)\n```\n\nIf the `wsgi_module` is not enabled, refer to online resources on how to enable it.\n\n## 3. Download `acme2certifier` from [master](https://github.com/grindsa/acme2certifier/archive/refs/heads/master.tar.gz) and Unpack It\n\n## 4. Install the Required Python Modules via `pip`\n\n```bash\nsudo pip3 install -r requirements.txt\n```\n\n## 5. Copy the Apache WSGI Configuration File\n\nCopy `examples/apache2/apache_wsgi.conf` to `/etc/apache2/sites-available/acme2certifier.conf` and modify it according to your needs.\n\n## 6. Enable TLS (Optional)\n\nIf you want to enable TLS, copy `examples/acme_wsgi_ssl.conf` to `/etc/apache2/sites-available/acme2certifier.conf` and modify it accordingly. Ensure you place the key bundle correctly. This file must contain the following certificate data in PEM format:\n\n- The private key\n- The end-entity certificate\n- Intermediate CA certificates (sorted from leaf to root, excluding the root CA certificate for security reasons)\n\nActivate the SSL module:\n\n```bash\nsudo a2enmod ssl\n```\n\n## 7. Activate the Virtual Server(s)\n\n```bash\nsudo a2ensite acme2certifier.conf\nsudo a2ensite acme2certifier_ssl.conf\n```\n\n## 8. Create Required Directories and Copy Necessary Files\n\n### Create the Main Directory\n\n```bash\nsudo mkdir /var/www/acme2certifier\n```\n\n### Copy the WSGI Application\n\n```bash\nsudo cp examples/acme2certifier_wsgi.py /var/www/acme2certifier\n```\n\n### Copy Required Directories\n\n```bash\nsudo mkdir /var/www/acme2certifier/examples\nsudo cp -R examples/ca_handler/ /var/www/acme2certifier/examples/ca_handler\nsudo cp -R examples/eab_handler/ /var/www/acme2certifier/examples/eab_handler\nsudo cp -R examples/hooks/ /var/www/acme2certifier/examples/hooks\nsudo cp -R examples/acme_srv.cfg /var/www/acme2certifier/examples/\nsudo cp -R tools/ /var/www/acme2certifier/tools\n```\n\n## 9. Set Up the `acme_srv` Directory\n\n### Create the `acme_srv` Directory\n\n```bash\nsudo mkdir /var/www/acme2certifier/acme_srv\n```\n\n### Copy the Contents of `acme_srv`\n\n```bash\nsudo cp -R acme_srv/ /var/www/acme2certifier/acme_srv\n```\n\n### 10. Configure `acme_srv.cfg`\n\nCreate a configuration file `acme_srv.cfg` in `/var/www/acme2certifier/acme_srv`, or use the example stored in the `examples` directory.\n\nModify the [configuration file](acme_srv.md) according to your needs.\n\n## 11. Select and Configure the CA Handler\n\n(Optional) Choose the appropriate CA handler from `examples/ca_handler` and copy it to `/var/www/acme2certifier/acme_srv/ca_handler.py`.\n\nConfigure the CA handler in `acme_srv.cfg`. [Example for Insta Certifier](certifier.md).\n\n## 12. Activate the WSGI Database Handler\n\n```bash\nsudo cp /var/www/acme2certifier/examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n```\n\n## 13. Set Proper Permissions\n\nEnsure that all files and directories under `/var/www/acme2certifier` are owned by the web server user (`www-data` is used as an example):\n\n```bash\nsudo chown -R www-data:www-data /var/www/acme2certifier/\n```\n\nSet the correct permissions for the `acme_srv` directory:\n\n```bash\nsudo chmod a+x /var/www/acme2certifier/acme_srv\n```\n\n## 14. Remove the Default Apache Configuration and Restart Apache\n\n```bash\nsudo rm /etc/apache2/sites-enabled/000-default.conf\nsudo systemctl reload apache2\n```\n\n## 15. Verify Installation\n\nCheck if access to the directory resource works:\n\n```bash\ncurl http://127.0.0.1/directory\n```\n\nExpected response:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1/acme_srv/revokecert\"\n}\n```\n\n## 16. Enroll a Certificate\n\nTry enrolling a certificate using your preferred ACME client. If it fails, check your CA handler configuration, logs, and enable [debug mode](acme_srv.md) in `acme2certifier` for further investigation.\n"
  },
  {
    "path": "docs/install_deb.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title: DEB Installation on Ubuntu 22.04 -->\n\n# DEB Installation on Ubuntu 22.04\n\nThe Debian package is generic and supports running `acme2certifier` with either Apache2 or Nginx.\n\n## Installation with Apache2\n\n1. Download the latest [DEB package](https://github.com/grindsa/acme2certifier/releases).\n1. Install `acme2certifier` and Apache2 packages:\n\n```bash\nsudo apt-get install -y apache2 apache2-data libapache2-mod-wsgi-py3\nsudo apt-get install -y ../acme2certifier_<version>-1_all.deb\n```\n\n3. Copy and activate the Apache2 configuration file:\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\nsudo a2ensite acme2certifier\n```\n\n4. Copy and activate the Apache2 SSL configuration file (optional):\n\n```bash\nsudo cp /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\nsudo a2ensite acme2certifier_ssl\n```\n\n5. Create a configuration file `acme_srv.cfg` in `/var/www/acme2certifier/acme_srv/`, or use the example stored in the `examples` directory.\n\n1. Modify the [configuration file](acme_srv.md) according to your needs.\n\n1. Configure the CA handler as needed. [Example for Insta Certifier](certifier.md).\n\n1. Enable and start the Apache2 service:\n\n```bash\nsudo systemctl enable apache2.service\nsudo systemctl start apache2.service\n```\n\n9. Test the server by accessing the directory resource:\n\n```bash\ncurl http://<your-server-name>/directory\n```\n\nExpected response:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1:8000/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1:8000/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1:8000/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1:8000/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1:8000/acme_srv/revokecert\"\n}\n```\n\n10. Try enrolling a certificate using your favorite ACME client. If something does not work, enable debugging in `/var/www/acme2certifier/acme_srv/acme_srv.cfg` and check `/var/log/apache2/error.log` for errors.\n\n## Installation with Nginx\n\n1. Download the latest [DEB package](https://github.com/grindsa/acme2certifier/releases).\n1. Install `acme2certifier` and Nginx packages:\n\n```bash\nsudo apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3\nsudo apt-get install -y ../acme2certifier_<version>-1_all.deb\n```\n\n3. Adapt the Nginx configuration file for Ubuntu 22.04 and activate the configuration:\n\n```bash\nsudo sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv.conf\nsudo cp examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\nsudo rm /etc/nginx/sites-enabled/default\nsudo ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n```\n\n4. Modify and copy the uWSGI configuration files:\n\n```bash\nsudo sed -i \"s/\\/run\\/uwsgi\\/acme.sock/acme.sock/g\" examples/nginx/acme2certifier.ini\nsudo sed -i \"s/nginx/www-data/g\" examples/nginx/acme2certifier.ini\necho \"plugins=python3\" | sudo tee -a examples/nginx/acme2certifier.ini\nsudo cp examples/nginx/acme2certifier.ini /var/www/acme2certifier\n```\n\n5. Create the `acme2certifier` systemd service file:\n\n```bash\nsudo cat <<EOT > acme2certifier.service\n[Unit]\nDescription=uWSGI instance to serve acme2certifier\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=/var/www/acme2certifier\nEnvironment=\"PATH=/var/www/acme2certifier\"\nExecStart=uwsgi --ini acme2certifier.ini\n\n[Install]\nWantedBy=multi-user.target\nEOT\n```\n\n6. Move the systemd service file:\n\n```bash\nsudo mv acme2certifier.service /etc/systemd/system/acme2certifier.service\n```\n\n7. Enable and start the `acme2certifier` service:\n\n```bash\nsudo systemctl start acme2certifier\nsudo systemctl enable acme2certifier\n```\n\n8. Enable and start Nginx:\n\n```bash\nsudo systemctl start nginx\nsudo systemctl enable nginx\n```\n\n9. Test the server by accessing the directory resource:\n\n```bash\ncurl http://<your-server-name>/directory\n```\n\nExpected response:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1:8000/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1:8000/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1:8000/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1:8000/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1:8000/acme_srv/revokecert\"\n}\n```\n\n10. Try enrolling a certificate using your favorite ACME client. If something does not work, enable debugging in `/var/www/acme2certifier/acme_srv/acme_srv.cfg` and check `/var/log/nginx/error.log` for errors.\n"
  },
  {
    "path": "docs/install_docker.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Containerized installation -->\n\n# Containerized installation using apache2/nginx as webserver and wsgi or django\n\n[acme2certifer in Docker](../examples/Docker)\n"
  },
  {
    "path": "docs/install_nginx_wsgi.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title: Installation on NGINX Running on Alma Linux 9 -->\n\n# Installation on NGINX Running on Alma Linux 9\n\nThe setup is designed so that uWSGI serves `acme2certifier`, while NGINX acts as a reverse proxy for better connection handling.\n\nA [ready-made shell script](../examples/install_scripts/a2c-centos9-nginx.sh) performing the tasks below can be found in the `examples/install_scripts` directory.\n\n## 1. Download and Extract the Archive\n\n```bash\ncd /tmp\ncurl https://codeload.github.com/grindsa/acme2certifier/tar.gz/refs/heads/master -o a2c-master.tgz\ntar xvfz a2c-master.tgz\ncd /tmp/acme2certifier-master\n```\n\n## 2. Install Required Packages\n\n```bash\nsudo yum install -y epel-release\nsudo yum update -y\nsudo yum install -y python-pip nginx python3-uwsgidecorators.x86_64 tar uwsgi-plugin-python3 policycoreutils-python-utils\n```\n\n## 3. Set Up the Project Directory\n\n```bash\nsudo mkdir /opt/acme2certifier\n```\n\n## 4. Install Required Python Modules\n\n```bash\nsudo pip install -r /opt/acme2certifier/requirements.txt\n```\n\n## 5. Configure `acme2certifier`\n\n1. Create a configuration file `acme_srv.cfg` in `/opt/acme2certifier/acme_srv/`, or use the example stored in the `examples` directory.\n1. Modify the [configuration file](acme_srv.md) according to your needs.\n1. Set the `handler_file` parameter in `acme_srv.cfg`, or copy the appropriate CA handler from `/opt/acme2certifier/examples/ca_handler/` to `/opt/acme2certifier/acme_srv/ca_handler.py`.\n1. Configure the connection to your CA server. [Example for Insta Certifier](certifier.md).\n\n## 6. Activate the WSGI Database Handler\n\n```bash\nsudo cp /opt/acme2certifier/examples/db_handler/wsgi_handler.py /opt/acme2certifier/acme_srv/db_handler.py\n```\n\n## 7. Copy the WSGI Application File\n\n```bash\nsudo cp /opt/acme2certifier/examples/acme2certifier_wsgi.py /opt/acme2certifier/\n```\n\n## 8. Set Correct Permissions\n\n```bash\nsudo chmod a+x /opt/acme2certifier/acme_srv\nsudo chown -R nginx /opt/acme2certifier/acme_srv\n```\n\n## 9. Test `acme2certifier` by Starting the Application\n\n```bash\ncd /opt/acme2certifier\nsudo uwsgi --http-socket :8000 --plugin python3 --wsgi-file acme2certifier_wsgi.py\n```\n\n## 10. Verify Directory Access\n\nRun the following command in a parallel session to confirm that everything is working:\n\n```bash\ncurl http://127.0.0.1:8000/directory\n```\n\nExpected response:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1:8000/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1:8000/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1:8000/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1:8000/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1:8000/acme_srv/revokecert\"\n}\n```\n\n## 11. Set Up uWSGI\n\n1. Create a uWSGI configuration file, or use the one stored in `examples/nginx`:\n\n```bash\nsudo cp examples/nginx/acme2certifier.ini /opt/acme2certifier\n```\n\n2. Enable the Python3 module in the uWSGI configuration file:\n\n```bash\necho \"plugins = python3\" | sudo tee -a examples/nginx/acme2certifier.ini\n```\n\n3. Create a Systemd Unit File for uWSGI, or use the one in `examples/nginx`:\n\n```bash\nsudo cp examples/nginx/uwsgi.service /etc/systemd/system/\nsudo systemctl enable uwsgi.service\n```\n\n4. Start uWSGI as a service:\n\n```bash\nsudo systemctl start uwsgi\n```\n\n## 12. Configure NGINX as a Reverse Proxy\n\n1. Use the example stored in `examples/nginx` and modify it as needed:\n\n```bash\nsudo cp examples/nginx/nginx_acme.conf /etc/nginx/conf.d/acme.conf\n```\n\n2. Restart NGINX:\n\n```bash\nsudo systemctl restart nginx\n```\n\n## 13. Adapt SELinux Configuration\n\nApply a customized policy to allow NGINX to communicate with uWSGI over Unix sockets:\n\n```bash\nsudo checkmodule -M -m -o acme2certifier.mod examples/nginx/acme2certifier.te\nsudo semodule_package -o acme2certifier.pp -m acme2certifier.mod\nsudo semodule -i acme2certifier.pp\n```\n\n## 14. Test the Server\n\n```bash\ncurl http://<your-server-name>/directory\n```\n\nThe above command may result in an error if the SELinux configuration still needs adjustment.\n"
  },
  {
    "path": "docs/install_nginx_wsgi_ub22.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title Installation on Nginx Running on Ubuntu 22.04 -->\n\n# Installation on Nginx Running on Ubuntu 22.04\n\nA [ready-made shell script](../examples/install_scripts/a2c-ubuntu22-nginx.sh) performing the tasks below can be found in the `examples/install_scripts` directory.\n\n## Steps\n\n### 1. Install Nginx and the Corresponding WSGI Module\n\n```bash\nsudo apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 curl krb5-user libgssapi-krb5-2 libkrb5-3 python3-gssapi\n```\n\n### 2. Download Acme2Certifier from [GitHub](https://github.com/grindsa/acme2certifier/archive/refs/heads/master.tar.gz) and Unpack It\n\n### 3. Install the Missing Python Modules via Pip\n\n```bash\nsudo pip3 install -r requirements.txt\n```\n\n### 4. Copy the Required Files and Directories\n\n```bash\nsudo cp examples/acme2certifier_wsgi.py /var/www/acme2certifier/acme2certifier_wsgi.py\nsudo cp -R examples/ca_handler/ /var/www/acme2certifier/examples/ca_handler\nsudo cp -R examples/eab_handler/ /var/www/acme2certifier/examples/eab_handler\nsudo cp -R examples/hooks/ /var/www/acme2certifier/examples/hooks\nsudo cp -R examples/nginx/ /var/www/acme2certifier/examples/nginx\nsudo cp examples/acme_srv.cfg /var/www/acme2certifier/examples/\nsudo cp -R acme_srv/ /var/www/acme2certifier/acme_srv\nsudo cp -R tools/ /var/www/acme2certifier/tools\nsudo cp examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n```\n\n### 5. Adapt and Activate the Nginx Configuration File\n\n```bash\nsudo sed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv.conf\nsudo cp examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\nsudo ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n```\n\n### 6. Adapt and Place the uWSGI Configuration File\n\n- The uWSGI socket file will be located in `/var/www/acme2certifier`.\n- The uWSGI daemon will run under the `www-data` user.\n- The uWSGI plugin for Python 3 must be activated.\n\n```bash\nsudo sed -i \"s/\\/run\\/uwsgi\\/acme.sock/acme.sock/g\" examples/nginx/acme2certifier.ini\nsudo sed -i \"s/nginx/www-data/g\" examples/nginx/acme2certifier.ini\nsudo echo \"plugins=python3\" >> examples/nginx/acme2certifier.ini\nsudo cp examples/nginx/acme2certifier.ini /var/www/acme2certifier\n```\n\n### 7. Pick the Correct CA Handler and Copy It\n\nSelect the appropriate CA handler from the `examples/ca_handler` directory and copy it to:\n\n```bash\nsudo cp examples/ca_handler/<your_ca_handler>.py /var/www/acme2certifier/acme_srv/ca_handler.py\n```\n\n### 8. Configure the CA Handler in `acme_srv.cfg`\n\nRefer to the [Example for Insta Certifier](certifier.md).\n\n### 9. Ensure Correct Ownership of Files and Directories\n\n```bash\nsudo chown -R www-data:www-data /var/www/acme2certifier/\n```\n\n### 10. Set Correct Permissions for the `acme_srv` Subdirectory\n\n```bash\nsudo chmod a+x /var/www/acme2certifier/acme_srv\n```\n\n### 11. Create and Install the uWSGI Service for Acme2Certifier\n\n```bash\ncat <<EOT > acme2certifier.service\n[Unit]\nDescription=uWSGI instance to serve Acme2Certifier\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=/var/www/acme2certifier\nEnvironment=\"PATH=/var/www/acme2certifier\"\nExecStart=uwsgi --ini acme2certifier.ini\n\n[Install]\nWantedBy=multi-user.target\nEOT\n\nsudo cp acme2certifier.service /etc/systemd/system/acme2certifier.service\n```\n\n### 12. Start and Enable the Acme2Certifier Service\n\n```bash\nsudo systemctl start acme2certifier\nsudo systemctl enable acme2certifier\n```\n\n### 13. Restart Nginx\n\n```bash\nsudo systemctl restart nginx\n```\n\n### 14. Verify the Services\n\nCheck if Nginx and uWSGI are up and running:\n\n```bash\ncurl http://127.0.0.1/directory\n```\n\nExpected output:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1/acme_srv/revokecert\"\n}\n```\n\n### 15. Enroll a Certificate\n\nUse your preferred ACME client to enroll a certificate. If it fails, check the CA handler configuration, logs, and enable [debug mode](acme_srv.md) in Acme2Certifier for troubleshooting.\n"
  },
  {
    "path": "docs/install_rpm.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title RPM Installation on AlmaLinux 9 -->\n\n# RPM Installation on AlmaLinux/Red Hat EL/CentOS Stream 9\n\n## 1. Download the Latest RPM Package\n\nDownload the latest [RPM package](https://github.com/grindsa/acme2certifier/releases).\n\n## 2. Install \"Extra Packages for Enterprise Linux (EPEL)\"\n\n```bash\nsudo yum install -y epel-release\nsudo yum update -y\n```\n\n## 3. Install the RPM Package\n\n```bash\nsudo yum -y localinstall /tmp/acme2certifier/acme2certifier-0.23.1-1.0.noarch.rpm\n```\n\n### Red Hat 8.x: Upgrade Required Packages\n\nIf installing on Red Hat 8.x, upgrade the following packages:\n\n- [python3-cryptography](https://cryptography.io/en/latest/) to version 36.0.1 or higher.\n- [python3-dns](https://www.dnspython.org/) to version 2.1 or higher.\n- [python3-jwcrypto](https://jwcrypto.readthedocs.io/en/latest/) to version 0.8 or higher.\n\nBackports of these packages from RHEL 9 can be found in the [A2C RPM repository](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8):\n\n- [python3-cryptography-36.0.1-4.el8.x86_64.rpm](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-cryptography-36.0.1-4.el8.x86_64.rpm)\n- [python3-dns-2.1.0-6.el8.noarch.rpm](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-dns-2.1.0-6.el8.noarch.rpm)\n- [python3-jwcrypto-0.8-4.el8.noarch.rpm](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-jwcrypto-0.8-4.el8.noarch.rpm)\n\n### Additional Modules for Specific CA Handlers\n\nDepending on your CA handler, you may need these additional modules:\n\n- [python3-impacket-0.11.0](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-impacket-0.11.0-2grindsa.el8.noarch.rpm) for [MS WCCE handler](https://github.com/grindsa/acme2certifier/blob/master/docs/mswcce.md).\n- [python3-ntlm-auth-1.5.0](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-ntlm-auth-1.5.0-2.el8.noarch.rpm) for [MS WSE handler](https://github.com/grindsa/acme2certifier/blob/master/docs/mscertsrv.md).\n- [python3-requests_ntlm-1.1.0](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-requests_ntlm-1.1.0-14.el8.noarch.rpm) for [MS WSE handler](https://github.com/grindsa/acme2certifier/blob/master/docs/mscertsrv.md).\n- [python3-requests-pkcs12-1.16](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-requests-pkcs12-1.16-1.el8.noarch.rpm) for [EST](https://github.com/grindsa/acme2certifier/blob/master/docs/est.md) or [EJBCA](https://github.com/grindsa/acme2certifier/blob/master/docs/ejbca.md) handler.\n\n## 4. Copy the Nginx Configuration File\n\n```bash\nsudo cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d/\n```\n\n## 5. Copy the Nginx SSL Configuration File (Optional)\n\n```bash\nsudo cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d/\n```\n\n## 6. Create and Configure `acme_srv.cfg`\n\nCreate the configuration file in `/opt/acme2certifier/acme_srv/` or use the example provided in the `examples` directory.\n\nModify the [configuration file](acme_srv.md) according to your needs.\n\n## 7. Configure the CA Handler\n\nSet up the CA handler as needed. [Example for Insta Certifier](certifier.md).\n\n## 8. Enable and Start the Acme2Certifier Service\n\n```bash\nsudo systemctl enable acme2certifier.service\nsudo systemctl start acme2certifier.service\n```\n\n## 9. Enable and Start the Nginx Service\n\n```bash\nsudo systemctl enable nginx.service\nsudo systemctl start nginx.service\n```\n\n## 10. Verify the Server\n\nTest the directory resource:\n\n```bash\ncurl http://<your-server-name>/directory\n```\n\nExpected output:\n\n```json\n{\n  \"newAccount\": \"http://127.0.0.1:8000/acme_srv/newaccount\",\n  \"fa8b347d3849421ebc4b234205418805\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n  \"keyChange\": \"http://127.0.0.1:8000/acme_srv/key-change\",\n  \"newNonce\": \"http://127.0.0.1:8000/acme_srv/newnonce\",\n  \"meta\": {\n    \"home\": \"https://github.com/grindsa/acme2certifier\",\n    \"author\": \"grindsa <grindelsack@gmail.com>\"\n  },\n  \"newOrder\": \"http://127.0.0.1:8000/acme_srv/neworders\",\n  \"revokeCert\": \"http://127.0.0.1:8000/acme_srv/revokecert\"\n}\n```\n\n## 11. Enroll a Certificate\n\nUse your preferred ACME client to enroll a certificate. If an issue occurs, enable debugging in `/opt/acme2certifier/acme_srv/acme_srv.cfg` and check `/var/log/messages` for errors.\n"
  },
  {
    "path": "docs/manual_installation.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- wiki-title Manual Installation Guide for acme2certifier -->\n\n# Manual Installation Guide for acme2certifier\n\nThis guide provides step-by-step instructions for manually installing and configuring **acme2certifier** from source. These steps assume you have downloaded and extracted the source code to `/tmp/acme2certifier`.\n\n> **Note:** These instructions are based on an installation on Ubuntu 24.04. Adapting them to other Linux distributions should be straightforward, though package names and service management commands may vary slightly.\n\n______________________________________________________________________\n\n## 1. System Preparation\n\nUpdate your package lists and install required dependencies:\n\n```sh\napt-get update  # && apt-get upgrade\napt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 curl krb5-user libkrb5-3 python3-gssapi\n```\n\n______________________________________________________________________\n\n## 2. Install acme2certifier\n\nNavigate to the source directory and install Python dependencies:\n\n```sh\ncd /tmp/acme2certifier\npip3 install Cython --break-system-packages\npython3 setup.py install\n```\n\n## 3. Post-Installation File Setup (nginx in this example)\n\nCopy and link required files for the application and web server:\n\n```sh\ncp /var/lib/acme2certifier/examples/acme2certifier_wsgi.py /var/lib/acme2certifier\nln -s /var/lib/acme2certifier/volume/acme_srv.cfg /var/lib/acme2certifier/acme_srv/\nln -s /var/lib/acme2certifier/examples/db_handler/wsgi_handler.py /var/lib/acme2certifier/acme_srv/db_handler.py\n\ncp /var/lib/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\ncp /var/lib/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\nrm /etc/nginx/sites-enabled/default\nln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\nln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n\ncp /var/lib/acme2certifier/examples/nginx/acme2certifier.ini /var/lib/acme2certifier\n\nchown -R www-data:www-data /var/lib/acme2certifier/\n```\n\n______________________________________________________________________\n\n## 4. Copy and configure the database handler\n\nLink your preferred database-handler into `/var/lib/acme2certifier/acme_srv`\n\n```sh\nln -s /var/lib/acme2certifier/examples/db_handler/[wsgi|django]_handler.py /var/lib/acme2certifier/acme_srv/db_handler.py\n```\n\nWhen using the django handler configure install and configure the django environment.\n\n```sh\napt-get install -y python3-django python3-mysqldb python3-pymysql python3-yaml\ncp -R /var/lib/acme2certifier/examples/django/* /var/lib/acme2certifier/\nsed -i \"s/acme2certifier_wsgi/acme2certifier.wsgi/g\" /var/lib/acme2certifier/acme2certifier.ini\n```\n\nModify the `settings.py` according to your needs, create the database tables and load the fixtures.\n\n```sh\ncd /var/lib/acme2certifier\npython3 manage.py makemigrations\npython3 manage.py migrate\npython3 manage.py loaddata acme_srv/fixture/status.yaml\nchown -R www-data:www-data /var/lib/acme2certifier/\n```\n\n## 5. Create systemd Service\n\nCreate the following systemd service file at `/etc/systemd/system/acme2certifier.service`:\n\n```ini\n[Unit]\nDescription=uWSGI instance to serve acme2certifier\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=/var/lib/acme2certifier\nEnvironment=\"PATH=/var/lib/acme2certifier\"\nExecStart=uwsgi --ini acme2certifier.ini\n\n[Install]\nWantedBy=multi-user.target\n```\n\n______________________________________________________________________\n\n## 6. Start and Enable Services\n\nStart and enable the acme2certifier service and restart nginx:\n\n```sh\nsystemctl start acme2certifier\nsystemctl enable acme2certifier\nsystemctl restart nginx\n```\n\nTo restart or stop the services later, use:\n\n```sh\nsystemctl restart acme2certifier\nsystemctl restart nginx\n\nsystemctl stop acme2certifier\nsystemctl stop nginx\n\nsystemctl start acme2certifier\nsystemctl start nginx\n```\n\n______________________________________________________________________\n\n## 7. Test with lego Client\n\nYou can test your ACME server using the lego client:\n\n```sh\ndocker run -i -v /home/joern/data/lego:/.lego/ --network acme --rm --name lego goacme/lego \\\n  -s http://acme-srv.acme -a --email \"lego@example.com\" \\\n  -d lego.acme --key-type rsa2048 --tls-skip-verify --http run\n```\n\n______________________________________________________________________\n\n**acme2certifier** should now be installed and running. For further configuration, refer to the project documentation.\n"
  },
  {
    "path": "docs/mscertsrv.md",
    "content": "<!-- markdownlint-disable MD013 MD014 -->\n\n<!-- wiki-title CA Handler for Microsoft Certification Authority Web Enrollment Service -->\n\n# CA Handler for Microsoft Certification Authority Web Enrollment Service\n\nThis CA handler uses Microsoft's [Certification Authority Web Enrollment Service](<https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831649(v=ws.11)>) for certificate enrollment. It also utilizes a modified version of the Python library [magnuswatn](https://github.com/magnuswatn/)/[certsrv](https://github.com/magnuswatn/certsrv) to communicate with the enrollment service.\n\n## Limitations\n\nBe aware of the following limitations when using this handler:\n\n- Authentication towards the Web Enrollment Service is limited to \"basic,\" \"NTLM,\" or \"GSSAPI (Kerberos).\" ClientAuth is not supported.\n- Communication is limited to HTTPS.\n- Revocation operations are not supported.\n\n## Preparation\n\n1. Microsoft Certification Authority Web Enrollment Service must be enabled and configured.\n1. You need a set of credentials with permission to access the service and enrollment templates.\n1. The authentication method (basic or NTLM) must be configured correctly.\n1. *(Optional)*: If installing from RPM and using NTLM authentication, you need two additional Python modules: [python3-requests-ntlm](https://pypi.org/project/requests_ntlm/) and [python3-ntlm-auth](https://pypi.org/project/ntlm-auth/). These are not part of the standard or EPEL repositories. You can find them in the [A2C GitHub repository](https://github.com/grindsa/sbom/tree/main/rpm-repo/RPMs).\n1. *(Optional)*: If installing from RPM and using GSSAPI authentication, you need two additional Python modules: [python3-requests-gssapi](https://pypi.org/project/requests-gssapi/) and [gssapi](https://pypi.org/project/gssapi/). These are also available in the [A2C GitHub repository](https://github.com/grindsa/sbom/tree/main/rpm-repo/RPMs).\n\n### Verifying Service Access\n\nBefore configuring **acme2certifier**, verify access to the Web Enrollment Service:\n\n- **NTLM authentication**:\n\n```bash\ncurl -I --ntlm --user <user>:<password> -k https://<host>/certsrv/\n```\n\n- **Basic authentication**:\n\n```bash\ncurl -I --user <user>:<password> -k https://<host>/certsrv/\n```\n\n- **GSSAPI authentication**:\n\n```bash\nexport KRB5_CONFIG=<path>/krb5.conf\nkinit <username>\ncurl --negotiate -u: <user>:<password> -k https://<host>/certsrv/\n```\n\nIf the service is accessible, the response should return status code **200**:\n\n```bash\nHTTP/1.1 200 OK\nCache-Control: private\nContent-Length: 3686\nContent-Type: text/html\nServer: Microsoft-IIS/10.0\nSet-Cookie: - removed - ; secure; path=/\nX-Powered-By: ASP.NET\n```\n\n### Extended Protection for Authentication (EPA) Configuration\n\nWhen using GSSAPI (Kerberos) authentication, you may encounter issues if the Microsoft Certificate Services Web Enrollment Service has Extended Protection for Authentication (EPA) set to \"Required\". The current `requests-gssapi` library does not support EPA in \"Required\" mode, and the [developers are working on implementing this feature](https://github.com/pythongssapi/requests-gssapi/pull/57).\n\n**Solution**: Change the EPA setting from \"Required\" to \"Accept\" in the IIS configuration for the Certificate Services Web Enrollment Service.\n\nTo modify the EPA setting:\n\n1. Open **Internet Information Services (IIS) Manager** on the server hosting the Certificate Services Web Enrollment Service\n1. Navigate to the **Default Web Site** → **CertSrv** application\n1. Double-click on **Authentication** in the Features View\n1. Select **Windows Authentication** and click **Advanced Settings**\n1. In the **Extended Protection** dropdown, change from **Required** to **Accept**\n1. Click **OK** to apply the changes\n1. Restart the IIS service or the specific application pool\n\nFor detailed information about Extended Protection for Authentication, refer to the [Microsoft documentation on Extended Protection for Authentication Overview](https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/authentication/windowsauthentication/extendedprotection/).\n\n**Note**: This configuration change maintains security while ensuring compatibility with the current `requests-gssapi` implementation. The EPA feature in \"Accept\" mode still provides protection against authentication relay attacks when supported by the client.\n\n## Installation\n\n- Allow the MD4 algorithm in `openssl.cnf`:\n\n```bash\nsudo sed -i \"s/default = default_sect/\\default = default_sect\nlegacy = legacy_sect/g\" /etc/ssl/openssl.cnf && sudo sed -i \"s/\\[default_sect\\]/\\[default_sect\\]\nactivate = 1\n\\[legacy_sect\\]\nactivate = 1/g\" /etc/ssl/openssl.cnf\n```\n\n- Install [certsrv](https://github.com/magnuswatn/certsrv) via pip (this module is already included in the Docker images):\n\n```bash\npip install certsrv[ntlm]\n```\n\n- Modify the server configuration (`acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/mscertsrv_ca_handler.py\nhost: <hostname>\nuser: <username>\npassword: <password>\nca_bundle: <filename>\nauth_method: <basic|ntlm|gssapi>\ntemplate: <name>\nallowed_domainlist: [\"example.com\", \"*.example2.com\"]\nkrb5_config: <path_to_individual>/krb5.conf\n```\n\n### Parameter Explanations\n\n- **host** – The hostname of the system providing the Web Enrollment Service.\n- **host_variable** *(optional)* – Name of the environment variable containing the host address (overridden if `host` is set in `acme_srv.cfg`).\n- **user** – Username for accessing the service.\n- **user_variable** *(optional)* – Name of the environment variable containing the username (overridden if `user` is set in `acme_srv.cfg`).\n- **password** – Password for authentication.\n- **password_variable** *(optional)* – Name of the environment variable containing the password (overridden if `password` is set in `acme_srv.cfg`).\n- **ca_bundle** – CA certificate bundle in PEM format, required for validating the server certificate.\n- **auth_method** – Authentication method (`basic`, `ntlm`, or `gssapi`).\n- **krb5_config** *(optional)* – Path to an individual `krb5.conf` file.\n- **template** – Certificate template used for enrollment.\n- **allowed_domainlist** *(optional)* – List of allowed domain names for enrollment (JSON format).\n- **enrollment_config_log** *(optional)* – Log enrollment parameters (default: `False`).\n- **enrollment_config_log_skip_list** *(optional)* – List of enrollment parameters to exclude from logs (JSON format).\n\n## Passing a Template from Client to Server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `template` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"template1\": \"http://foo.bar/template1\", \"template2\": \"http://foo.bar/template2\", \"template3\": \"http://foo.bar/template3\"}\n```\n\nOnce enabled, a client can specify the template to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile template2\n```\n\nThe handler supports the [header_info_list feature](header_info.md), allowing an ACME client to specify a template name during enrollment. To enable this feature, update `acme_srv.cfg`:\n\n```ini\n[Order]\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\n### Example Usage\n\n- **acme.sh**:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent template=foo --debug 3 --output-insecure\n```\n\n- **lego**:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent template=foo -d <fqdn> --http run\n```\n\n## EAB Profiling\n\nThis handler supports [EAB profiling](eab_profiling.md) to allow individual enrollment configurations per ACME account, as well as restrictions on CN and SANs in the CSR. To enable it, configure `acme_srv.cfg` as follows:\n\n```ini\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\n### Example Key File\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"template\": [\"WebServerModified\", \"WebServer\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.local\"]\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"template\": \"WebServerModified\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.local\"],\n      \"unknown_key\": \"unknown_value\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n\nThis setup ensures that individual accounts can have specific enrollment configurations and domain restrictions.\n"
  },
  {
    "path": "docs/mswcce.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Handler for Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE) -->\n\n# CA Handler for Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE)\n\nThis CA handler uses the Microsoft [Windows Client Certificate Enrollment Protocol](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/446a0fca-7f27-4436-965d-191635518466). The handler incorporates code from [Certipy](https://github.com/ly4k/Certipy), a pentesting tool for Active Directory Certificate Services (AD-CS).\n\n## Limitations\n\nBe aware of the following limitations when using this handler:\n\n- CA certificates cannot be fetched from the CA server and must be manually loaded via the `ca_bundle` option in `acme_srv.cfg`.\n- Revocation operations are not yet supported.\n\n## Preparation\n\n1. Active Directory Certificate Services (AD-CS) must be enabled and properly configured.\n1. The CA handler uses RPC/DCOM to communicate with the CA server, so the CA server must be accessible via **TCP port 445**.\n1. *(Optional)*: If installing from RPM or DEB and planning to use Kerberos authentication, ensure you have an updated [Impacket module (version 0.11 or higher)](https://github.com/fortra/impacket), as older versions have issues handling UTF-8 encoded passwords. You can find updated packages in the [A2C GitHub repository](https://github.com/grindsa/sbom/tree/main/rpm-repo/RPMs).\n1. You need a set of credentials with sufficient permissions to access the service and enrollment templates.\n\n## Local Installation\n\n- Install the [Impacket](https://github.com/fortra/impacket) module.\n\n### **Important:**\n\nSome malware scanners, such as Microsoft Defender, classify Impacket as a hacking tool (see [Fortra Impacket Issue #1762](https://github.com/fortra/impacket/issues/1762) or [Fortra Impacket Issue #1271](https://github.com/fortra/impacket/issues/1271#issuecomment-1058729047)). These alerts are triggered mainly by example scripts included in the package, not the library itself.\n\nTo avoid issues with your security team, consider installing a stripped-down version of Impacket without flagged scripts. Pre-packaged versions are available for [RHEL 8](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel8/python3-impacket-0.11.0-2grindsa.el8.noarch.rpm) and [RHEL 9](https://github.com/grindsa/sbom/raw/main/rpm-repo/RPMs/rhel9/python3-impacket-0.11.0-2grindsa.el9.noarch.rpm) in the [SBOM repository](https://github.com/grindsa/sbom/tree/main/rpm-repo).\n\nIf installing from pip or source, follow these steps:\n\n- Download the Impacket package:\n\n```bash\npip3 download impacket --no-deps\n```\n\n- Unpack the archive:\n\n```bash\ntar xvfz impacket-0.11.0.tar.gz\n```\n\n- Remove all files and subdirectories in the `examples` directory:\n\n```bash\nrm -rf impacket-0.11.0/examples/*\n```\n\n- Install the package:\n\n```bash\npython3 setup.py install\n```\n\n## Configuration\n\nModify the server configuration (`acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/mswcce_ca_handler.py\nhost: <hostname>\nuser: <username>\npassword: <password>\ntarget_domain: <domain_name>\ndomain_controller: <IP_of_domain_controller>\nca_name: <ca_name>\nca_bundle: <filename>\ntemplate: <template_name>\ntimeout: 5\nuse_kerberos: False\nallowed_domainlist: [\"example.com\", \"*.example2.com\"]\n```\n\n### Parameter Explanations\n\n- **host** – The hostname of the system providing the enrollment service. Multiple hosts can be specified as `server1, server2, server3`; a random host will be selected.\n- **host_variable** *(optional)* – Environment variable containing the host address (overridden if `host` is set in `acme_srv.cfg`).\n- **ca_name** – Certificate authority name. Multiple CA names can be specified as `ca1, ca2, ca3`; a random entry will be chosen.\n- **user** – Username for accessing the service.\n- **user_variable** *(optional)* – Environment variable containing the username (overridden if `user` is set in `acme_srv.cfg`).\n- **password** – Password for authentication.\n- **password_variable** *(optional)* – Environment variable containing the password (overridden if `password` is set in `acme_srv.cfg`).\n- **target_domain** *(optional)* – Active Directory domain name.\n- **domain_controller** *(optional)* – IP address of the domain controller/DNS server.\n- **dns_server** *(optional)* – IP address of the DNS server.\n- **ca_bundle** – CA certificate chain in PEM format, provided along with the client certificate.\n- **template** – Certificate template used for enrollment.\n- **timeout** *(optional)* – Enrollment timeout in seconds (default: `5`).\n- **use_kerberos** – Use Kerberos for authentication. If `False`, authentication is done via NTLM. Due to Microsoft's [October 2023 announcement](https://techcommunity.microsoft.com/t5/windows-it-pro-blog/the-evolution-of-windows-authentication/ba-p/3926848), Kerberos is recommended, but NTLM remains the default for backward compatibility.\n- **allowed_domainlist** *(optional)* – List of allowed domains for enrollment (JSON format).\n- **enrollment_config_log** *(optional)* – Log enrollment parameters (default: `False`).\n- **enrollment_config_log_skip_list** *(optional)* – List of enrollment parameters to exclude from logs (JSON format).\n\n## Passing a Template from Client to Server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `template` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"template1\": \"http://foo.bar/template1\", \"template2\": \"http://foo.bar/template2\", \"template3\": \"http://foo.bar/template3\"}\n```\n\nOnce enabled, a client can specify the template to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile template2\n```\n\nFurther, this handler uses the [header_info_list feature](header_info.md), allowing an ACME client to specify a template name for certificate enrollment. To enable this feature, update `acme_srv.cfg`:\n\n```ini\n[Order]\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\n## Example Usage\n\n- **acme.sh**:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent template=foo --debug 3 --output-insecure\n```\n\n- **lego**:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent template=foo -d <fqdn> --http run\n```\n\n# EAB Profiling\n\nThis handler supports [EAB profiling](eab_profiling.md), which allows individual enrollment configurations per ACME account and restricts CN/SANs in the CSR. To enable this feature, update `acme_srv.cfg`:\n\n```ini\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\n## Example Key File\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"example_hmac_value\",\n    \"cahandler\": {\n      \"template\": [\"WebServerModified\", \"WebServer\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"unknown_key\": \"unknown_value\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"template\": \"WebServerModified\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"unknown_key\": \"unknown_value\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n\nThis setup ensures that individual accounts can have specific enrollment configurations and domain restrictions.\n"
  },
  {
    "path": "docs/nclm.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Handler for NetGuard Certificate Lifecycle Manager -->\n\n# Connecting to NetGuard Certificate Lifecycle Manager\n\n## Prerequisites\n\nEnsure the following conditions are met before configuring the connection:\n\n- **NCLM 24.2.0 or higher** must be up and running.\n- The **external REST API** must be enabled.\n- You must have a **username and password** to access NCLM via the REST service.\n- A **container must be created in NCLM** to store the certificates.\n\n## Configuration\n\nModify the server configuration file (`/acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/nclm_ca_handler.py\napi_host: http://<ip>:<port>\napi_user: <user>\napi_password: <password>\nca_bundle: <value>\nca_name: <ca_name>\ncontainer_name: <container_name>\ntemplate_name: <template_name>\n```\n\n### Parameter Explanations\n\n- **api_host** – URL of the Certifier REST service.\n- **api_user** – Username for the REST API.\n- **api_user_variable** *(optional)* – Environment variable containing the REST username (overridden if `api_user` is set in `acme_srv.cfg`).\n- **api_password** – Password for the REST API user.\n- **api_password_variable** *(optional)* – Environment variable containing the REST password (overridden if `api_password` is set in `acme_srv.cfg`).\n- **ca_bundle** *(optional)* – Certificate bundle used to validate the server certificate. Can be `True`, `False`, or a filename (default: `True`).\n- **ca_name** – Name of the CA used for certificate enrollment.\n- **container_name** – Name of the container where certificates will be stored.\n- **template_name** *(optional)* – Name of the template to be applied to the CSR.\n- **allowed_domainlist** *(optional)* – List of allowed domain names for enrollment (JSON format). Example: `[\"bar.local\", \"bar.foo.local\"]` (default: `[]`).\n- **enrollment_config_log** *(optional)* – Enable logging of enrollment parameters (default: `False`).\n- **enrollment_config_log_skip_list** *(optional)* – List of enrollment parameters to exclude from logs (JSON format). Example: `[\"parameter1\", \"parameter2\"]` (default: `[]`).\n"
  },
  {
    "path": "docs/openssl.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Handler for an OpenSSL-based CA Stored on Local File System -->\n\n# Support for an OpenSSL-based CA Stored on Local File System\n\nThe OpenSSL CA handler is primarily intended for testing and lab environments. **It is strongly recommended not to use it in production environments without reviewing the local system configuration and hardening measures.**\n\n## Prerequisites\n\nYou need to create a certificate authority (CA) on the local file system.\n\nThe following command generates a CA certificate and key:\n\n```bash\nopenssl req -x509 -new -extensions v3_ca -newkey rsa:4096 -keyout ca-key.pem -out ca-cert.pem -days 3650\n```\n\n## Installation and Configuration\n\n- **Create directories** to store CA certificates, keys, and certificate revocation lists (CRLs):\n\n```bash\nmkdir -p acme_srv/ca/certs\n```\n\n- **Move the generated key and certificate** into the CA directory:\n\n```bash\nmv ca-key.pem acme_srv/ca/\nmv ca-cert.pem acme_srv/ca/\n```\n\n- **Modify the server configuration** (`/acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/openssl_ca_handler.py\nissuing_ca_key: acme_srv/ca/ca-key.pem\nissuing_ca_key_passphrase: Test1234\nissuing_ca_cert: acme_srv/ca/ca-cert.pem\nissuing_ca_crl: acme_srv/ca/crl.pem\ncert_validity_days: 30\ncert_validity_adjust: True\ncert_save_path: acme_srv/ca/certs\nca_cert_chain_list: []\nopenssl_conf: acme_srv/ca/openssl.conf\nallowed_domainlist: [\"*.foo.bar\", \"*.bar.local\"]\nblocked_domainlist: [\"*.google.com.foo.bar\", \"host.foo.bar\", \"www.foo.bar\"]\nsave_cert_as_hex: True\ncn_enforce: True\n```\n\n### Parameter Explanations\n\n- **issuing_ca_key** – Private key of the issuing CA (PEM format) used to sign certificates and CRLs.\n- **issuing_ca_key_passphrase** – Password to access the private key.\n- **issuing_ca_key_passphrase_variable** *(optional)* – Name of the environment variable containing the CA key passphrase (overridden if `issuing_ca_key_passphrase` is set in `acme_srv.cfg`).\n- **issuing_ca_cert** – CA certificate in PEM format.\n- **issuing_ca_crl** – CA certificate revocation list (CRL) in PEM format.\n- **ca_cert_chain_list** – List of root and intermediate CA certificates to be included in the certificate chain (the issuing CA certificate should not be included).\n- **cert_validity_days** *(optional)* – Certificate validity period in days (default: `365`).\n- **cert_save_path** *(optional)* – Directory to store enrolled certificates.\n- **openssl_conf** *(optional)* – OpenSSL configuration file (`openssl.cnf`) containing certificate extensions.\n- **allowed_domainlist** *(optional)* – List of allowed common names (CNs) and Subject Alternative Names (SANs), formatted as regular expressions ([Python regex syntax](https://docs.python.org/3/library/re.html)). Stored in JSON format.\n- **blocked_domainlist** *(optional)* – List of prohibited CNs and SANs, formatted as regular expressions. Stored in JSON format.\n- **save_cert_as_hex** *(optional)* – If `True`, the certificate serial number will be stored in hexadecimal format as the filename (default: `False`).\n- **cn_enforce** *(optional)* – If `True`, the first SAN will be used as the CN if no CN is provided in the CSR (default: `False`).\n- **cert_validity_adjust** *(optional)* – If `True`, ensures that the \"valid until\" field of a certificate does not exceed the expiration date of any certificate in the certificate chain (default: `False`).\n\n### Domain Allow/Block Lists\n\nThe `allowed_domainlist` and `blocked_domainlist` options can be used independently. However, **if both are used together, the blocked domain list takes precedence**.\n\n## OpenSSL Configuration File\n\nThe `openssl_conf` file allows customization of the certificate profile. It must contain a section `[extensions]`, which specifies the certificate extensions.\n\nIf not specified, the following default extensions will be applied:\n\n```ini\n[extensions]\nsubjectKeyIdentifier    = hash, issuer:always\nkeyUsage                = digitalSignature, keyEncipherment\nbasicConstraints        = critical, CA:FALSE\nauthorityKeyIdentifier  = keyid:always, issuer:always\nextendedKeyUsage        = critical, clientAuth, serverAuth\n```\n\n## Notes\n\n- Certificates and CRLs will be signed using **SHA-256**.\n- During enrollment, **all extensions included in the CSR will be copied** to the issued certificate. *(This may be a security risk, but this handler is not recommended for production use.)*\n- The CRL \"next update interval\" is set to **7 days**.\n\nEnjoy enrolling and revoking certificates!\n"
  },
  {
    "path": "docs/openxpki.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Handler for OpenXPKI -->\n\n# Connecting to OpenXPKI\n\nThis handler allows certificate enrollment from [OpenXPKI](https://www.openxpki.org/), as ACME support appears to be available only in the commercial version.\n\nAlthough connecting to OpenXPKI was previously possible via the [generic EST CA handler](est.md), this dedicated handler is **preferred** because it supports revocation operations and allows specifying [certificate profiles](https://openxpki.readthedocs.io/en/master/configuration/profile.html).\n\n## Prerequisites\n\nTo use this handler, ensure you have:\n\n- A running [OpenXPKI](https://www.openxpki.org/) instance with an **activated [RPC server](https://openxpki.readthedocs.io/en/master/subsystems/rpc.html)**.\n- An RPC endpoint that supports `RequestCertificate`, `RevokeCertificate`, and `SearchCertificate`, as described in the [example configuration](https://github.com/openxpki/openxpki-config/blob/community/config.d/realm.tpl/rpc/generic.yaml).\n- A **client certificate and key** in PEM format for authentication with OpenXPKI.\n- A [certificate profile](https://openxpki.readthedocs.io/en/master/configuration/profile.html).\n\n## OpenXPKI Configuration\n\nTo ensure compatibility with **acme2certifier**, adjust the OpenXPKI configuration:\n\n### 1. Return the Full Certificate Chain\n\nBy default, acme2certifier expects a full certificate chain (including the root certificate) in the response to a `RequestCertificate` call. Modify the `export_certificate` parameter in the OpenXPKI endpoint configuration file (`config.d/realm.tpl/rpc/`) as follows:\n\n```yaml\npolicy:\n      export_certificate: fullchain\n```\n\n### 2. Configure Approval Points\n\nAlthough **certificate polling** is supported via the `polling_timeout` parameter in `acme_srv.cfg`, **manual or dual approval should be skipped** to ensure smooth enrollment operations. Set `approval_points` to `1` in `config.d/realm.tpl/rpc/`:\n\n```yaml\npolicy:\n      approval_points: 1\n```\n\n### 3. Handle Missing Common Names in CSRs\n\nSome ACME clients, such as [Certbot](https://certbot.eff.org/), generate CSRs without a subject name, causing OpenXPKI to reject them. To address this, modify the OpenXPKI certificate profile (`config.d/realm.tpl/profile/`) to use the **first Subject Alternative DNS name (SAN DNS.0) as the CN** when no CN is present:\n\n```yaml\nstyle:\n    # RPC endpoint name, e.g., \"enroll\"\n    enroll:\n        subject:\n            dn: \"[% IF CN.0 && CN.0 != '' %]CN=[% CN.0 %][% ELSE %]CN=[% SAN_DNS.0 %][% END %]\"\n```\n\n- acme2certifier will issue certificates on behalf of the end nodes. This needs to be allowed in OpenXPKI. Please see more info on enroll modes (especially `Signer on Behalf` section) here: [Enrollment Workflow](https://openxpki.readthedocs.io/en/master/configuration/workflows/enroll.html). For this, you will need to set the client certificate as an `authorized_signer` for your RPC endpoint. You can set this in `config.d/realm.tpl/rpc/enroll.yaml`:\n\n```yaml\nauthorized_signer:\n    rule1:\n        # Full DN\n        subject: CN=cn-of-your-client-cert-here(?:,.+|$)\n```\n\n## Configuration\n\nModify the **acme2certifier** configuration (`acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/openxpki_ca_handler.py\nhost: <URL>\nclient_key: <filename>\nclient_cert: <filename>\nca_bundle: <filename>\ncert_profile_name: <name>\nendpoint_name: <name>\npolling_timeout: <seconds>\n```\n\n### Parameter Explanations\n\n- **host** – URL of the OpenXPKI server.\n- **client_cert** – Client certificate in PEM or PKCS#12 format, used for authentication.\n- **client_key** – *(Required if using PEM format)* Key file used for authentication.\n- **cert_passphrase** – *(Required if using PKCS#12 format)* Passphrase for accessing the PKCS#12 container.\n- **cert_passphrase_variable** *(optional)* – Environment variable containing the certificate passphrase (overridden if `cert_passphrase` is set in `acme_srv.cfg`).\n- **ca_bundle** *(optional)* – CA certificate chain in PEM format needed to validate the OpenXPKI server certificate. Accepts `True`, `False`, or a filename (default: `True`).\n- **cert_profile_name** – Name of the OpenXPKI certificate profile to be used.\n- **endpoint_name** – Name of the OpenXPKI RPC endpoint.\n- **polling_timeout** – Timeout (in seconds) for enrollment operations (default: `0`, polling disabled).\n- **request_timeout** *(optional)* – Timeout (in seconds) for OpenXPKI requests (default: `5s`).\n- **allowed_domainlist** *(optional)* – List of domain names allowed for enrollment (JSON format). Example: `[\"bar.local\", \"bar.foo.local\"]` (default: `[]`).\n\n## Certificate Enrollment\n\nUse your preferred ACME client for certificate enrollment. A list of clients used in our regression testing is available in the [disclaimer section of our README](../README.md).\n\n## Passing a profile_id from client to server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `cert_profile_name` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"profile1\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}\n```\n\nOnce enabled, a client can specify the cert_profile_name to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile profile2\n```\n\nFurther, this handler makes use of the [header_info_list feature](header_info.md) allowing an ACME client to specify a certificate profile to be used during certificate enrollment. This feature is disabled by default and must be activated in `acme_srv.cfg` as shown below\n\n```config\n[Order]\n...\nheader_info_list: [\"HTTP_USER_AGENT\"]\n```\n\nThe ACME client can then specify the profileID as part of its user-agent string.\n\nExample for acme.sh:\n\n```bash\ndocker exec -i acme-sh acme.sh --server http://<acme-srv> --issue -d <fqdn> --standalone --useragent cert_profile_name=acme_clt --debug 3 --output-insecure\n```\n\nExample for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" --user-agent cert_profile_name=acme_clt -d <fqdn> --http run\n```\n\n## eab profiling\n\nThis handler can use the [eab profiling feature](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activatedd in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nBelow is an example key file used during regression testing:\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"cert_profile_name\": [\"acmeca2\", \"acmeca1\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"]\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"cert_profile_name\": \"acmeca2\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"ca_name\": \"acmeca\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/pkcs7_soap_ca.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title SOAP CA Handler Prototype -->\n\n# SOAP CA Handler\n\nThis handler is a **proof of concept** that enables certificate enrollment from a certificate authority that provides a SOAP interface. Certificate Signing Requests (CSRs) from ACME clients are encapsulated within a **PKCS#7** structure and digitally signed. The certificate corresponding to the signing key is also included.\n\nParts of the code used to create the PKCS#7 message are borrowed from [magnuswatn/pkcs7csr](https://github.com/magnuswatn/pkcs7csr).\n\n## Prerequisites\n\nEnsure you have the following:\n\n- A **certificate and private key** (PEM format) used to sign the PKCS#7 content.\n- **CA certificates** (PEM format) required to validate the certificate presented by the SOAP server.\n\n## Installation and Configuration\n\nModify the server configuration (`acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/pkcs7_soap_ca_handler.py\nsoap_srv: http[s]://<ip>:<port>\nsigning_key: <filename>\nsigning_cert: <filename>\nca_bundle: <filename>\nprofilename: <Profile Name>\nemail: <email address>\n```\n\n### Parameter Explanations\n\n- **soap_srv** – URL of the SOAP server.\n- **signing_key** – Private key of the certificate used to sign the PKCS#7 structure (`/path/to/key.pem`).\n- **signing_cert** – Certificate attached to the PKCS#7 message sent to the SOAP server (`/path/to/certificate.pem`).\n- **ca_bundle** – CA certificate bundle needed to validate the SOAP server certificate (`/path/to/ca_bundle.pem`). Set to `False` to disable certificate validation.\n- **profilename** – Name of the certificate profile to be inserted into the SOAP request.\n- **email** – Email address to be included in the SOAP request.\n\n## SOAP Messages\n\n### SOAP Request Sent by acme2certifier (NewCertRequest)\n\n```xml\n<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">\n   <soapenv:Header/>\n   <soapenv:Body>\n      <aur:RequestCertificate>\n         <aur:request>\n            <aur:ProfileName>profilename</aur:ProfileName>\n            <aur:CertificateRequestRaw>PKCS#7 message encoded in base64</aur:CertificateRequestRaw>\n            <aur:Email>email</aur:Email>\n            <aur:ReturnCertificateCaChain>true</aur:ReturnCertificateCaChain>\n         </aur:request>\n      </aur:RequestCertificate>\n   </soapenv:Body>\n</soapenv:Envelope>\n```\n\n### SOAP Response Sent by Server Upon Successful Enrollment (NewCertResponse)\n\n```xml\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <s:Body>\n    <RequestCertificateResponse>\n      <RequestCertificateResult xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">\n        <IssuedCertificate>certificate chain in PKCS#7 format</IssuedCertificate>\n      </RequestCertificateResult>\n    </RequestCertificateResponse>\n  </s:Body>\n</s:Envelope>\n```\n\n### SOAP Response Sent by Server in Case of Failure\n\n```xml\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <s:Body>\n    <s:Fault>\n      <faultcode>s:Client</faultcode>\n      <faultstring>Processing RequestCertificate - Error! by request={ProfileName=profilename,CertificateRequestRaw.Length=<length>,Email=email,ReturnCertificateCaChain=True}, profile=profilename, pkcs7initials=, ErrorMessage=Cannot parse PKCS7 message!</faultstring>\n    </s:Fault>\n  </s:Body>\n</s:Envelope>\n```\n"
  },
  {
    "path": "docs/poll.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Polling to Check Pending Enrollment Requests -->\n\n# `Ca_handler.poll()`\n\nThe `poll` method has been implemented to support use cases where certificate issuance requires manual approval by the CA administrator.\n\nIn such cases, **acme2certifier** marks the status of the order resource as **\"processing\"** and includes a **\"Retry-After\"** header in the response to an order status polling request, as described in [RFC 8555, Section 7.4](https://tools.ietf.org/html/rfc8555#section-7.4).\n\nAdditionally, when a CSR enters the **\"pending\"** state, it is assumed that the CA server provides information in the enrollment response that can be used to look up the request's status. This information is returned by the `ca.handler.enroll()` method (stored in the variable `poll_identifier`) and is saved in the database alongside the CSR in the **`certificate`** table under the **`poll_identifier`** field.\n\n## Polling Implementation\n\nThe script [`cert_poll.py`](../tools/cert_poll.py) is located in the **tools** directory and can be scheduled via **cron**. It scans the **`orders`** table for orders with status **\"processing\" (4)** and passes the `poll_identifier`, along with other necessary information, to the `certificate.poll()` method.\n\n### `ca_handler.poll()` Responsibilities\n\nThe `ca_handler.poll()` method:\n\n1. **Checks the status of the CSR** on the CA server.\n\n1. **Downloads the certificate** if it is available.\n\n1. **Builds the certificate chain** and returns the following details to `certificate.poll()`, which then updates the database:\n\n   - An **error message**, if any.\n   - The **certificate chain** in PEM format.\n   - The **certificate** in ASN.1 (binary) format, Base64-encoded (needed for revocation).\n   - An **updated poll_identifier**.\n   - An indication (`True`/`False`) of whether the CSR was rejected.\n\n### Status Updates\n\n- If the certificate is issued, the **order status** is set to **\"valid\"**, and a **URL to the `certificate` resource** is provided when an ACME client polls the `order` resource.\n- If the CSR is rejected, the **order status** changes to **\"invalid\"**.\n\n## Example Implementations\n\nAn example implementation is available in the handler for **[NCLM/Insta Certifier](certifier.md)**.\n\nAdditionally, an **[example `acme_srv.db`](../examples/acme_srv.db.example)** is provided to give insight into expected values, particularly in the **certificate** table.\n"
  },
  {
    "path": "docs/prevalidated_domainlist.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title Prevalidated Domain List Feature for ACME Authorization -->\n\n# Prevalidated Domain List Feature for ACME Authorization\n\n## Overview\n\nThe `prevalidated_domainlist` feature in the ACME Authorization module allows a2c-administrators to specify a list of DNS domains that are considered pre-authorized (prevalidated) for certificate issuance. When enabled, any ACME authorization request for a domain in this list will be automatically marked as valid, bypassing the standard ACME challenge validation process.\n\nThis feature is intended for special use cases where certain domains are trusted by policy or infrastructure, and strict validation is not required. **It introduces significant security risks if misused.**\n\n## How It Works\n\n- When a new authorization request is processed, the Authorization class checks if the requested DNS identifier matches any entry in the `prevalidated_domainlist`.\n\n- If a match is found, the authorization status is set to `valid` immediately, and the associated order gets marked as `ready`. ACME Clients following RFC8555 will then skip the challenge validation process and directly finalize the order by submitting a CSR.\n\n- The feature can be enabled in two ways:\n\n  - **Direct Configuration:** By setting the `prevalidated_domainlist` option in the `acme_srv.cfg` configuration file.\n\n  - **ACME Profiling (EAB Profile):** By enabling EAB profiling, which can dynamically provide a `prevalidated_domainlist` for specific accounts via the EAB handler/profile mechanism.\n\n## Enabling the Feature\n\n### 1. Direct Configuration in `acme_srv.cfg`\n\nAdd the following to your `[Authorization]` section:\n\n```ini\n[Authorization]\nprevalidated_domainlist: [\"example.com\", \"trusted.example.org\"]\n```\n\n- The value must be a valid JSON array of domain names.\n- Restart the ACME service after changing the configuration.\n\n### 2. Enabling via ACME Profiling (EAB Profile)\n\nIf you use EAB (External Account Binding) profiles, you can provide a `prevalidated_domainlist` for specific accounts. This is controlled by the `eab_profiling` and `eab_handler` options, and the profile data structure must include the domain list:\n\nExample EAB profile snippet:\n\n```json\n{\n  \"keyid_03\": {\n    \"authorization\": {\n      \"prevalidated_domainlist\": [\"profiled.example.com\"]\n    }\n  }\n}\n```\n\nWhen an account with key ID `keyid_03` requests authorization, the specified domains will be approved for that account.\n\n## Security Implications\n\n**Warning: Enabling the prevalidated domain list feature can severely weaken the security of your ACME deployment.**\n\n- Any domain listed in `prevalidated_domainlist` will be issued certificates without proof of control.\n- If the list is set globally, any client can obtain certificates for those domains.\n- Use this feature only in tightly controlled environments, such as internal PKIs or for legacy migration scenarios.\n- Always audit and restrict the list to the minimum set of domains required.\n- Consider using EAB profiling to scope prevalidation to specific accounts rather than globally.\n\n## Example Configuration\n\n**acme_srv.cfg:**\n\n```ini\n[Authorization]\nprevalidated_domainlist = [\"internal.example.com\", \"vpn.example.com\"]\n```\n\n**EAB Profile JSON:**\n\n```json\n{\n  \"special_kid\": {\n    \"authorization\": {\n      \"prevalidated_domainlist\": [\"special.example.com\"]\n    }\n  }\n}\n```\n\n## References\n\n- See the `Authorization` class in `acme_srv/authorization.py` for implementation details.\n- For EAB profiling, refer to your EAB handler and profile documentation.\n\n______________________________________________________________________\n\n**Again: Use with extreme caution.** This feature is for advanced administrators who understand the security trade-offs.\n"
  },
  {
    "path": "docs/proxy_support.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title Proxy Support in acme2certifier -->\n\n# Proxy Support in acme2certifier\n\nProxy support was introduced in **acme2certifier** version **0.18**.\n\nCurrently, both **HTTP** and **SOCKS5** proxies are supported for:\n\n- **Validation of HTTP and TLS-ALPN challenges**\n- **Usage in the following CA handlers:**\n  - `certifier_ca_handler.py`\n  - `est_ca_handler.py`\n  - `mscertsrv_ca_handler.py`\n\n## Configuration\n\nProxies are configured in `acme_srv/acme_srv.cfg` and must be set **per destination**.\n\nExample configuration:\n\n```ini\n[DEFAULT]\ndebug: True\nproxy_server_list: {\"bar.local$\": \"socks5://proxy.dmn:1080\", \"foo.local$\": \"socks5://proxy.dmn:1080\"}\n```\n\n### Supported Destination Formats\n\nA **destination** can be defined as:\n\n- A **TLD** (e.g., `.local`)\n- A **domain name** (e.g., `bar.local`)\n- A **fully qualified domain name (FQDN)** (e.g., `foo.bar.local`)\n\n### Wildcards and Regular Expressions\n\n- Wildcards are supported:\n  Example: `host*.bar.local`\n- Regular expressions are also supported:\n  Example: `^hostname.bar.local$`\n\n### Global Proxy Configuration\n\nTo configure a proxy for **all outbound connections**, use a **single asterisk (`*`)**:\n\n```ini\nproxy_server_list: {\"*\": \"socks5://proxy.dmn:1080\"}\n```\n"
  },
  {
    "path": "docs/rfc8823_email_identifier.md",
    "content": "<!-- markdownlint-disable  MD013 -->\n\n<!-- wiki-title Enrollment of End-User Certificates according to RFC8823 -->\n\n# Enrollment of End-User Certificates according to RFC8823\n\n## Introduction\n\nThis feature adds support for the enrollment of **End-User certificates** via the ACME protocol, following [RFC 8823: End-User Certificate Enrollment for the Automated Certificate Management Environment (ACME)](https://datatracker.ietf.org/doc/html/rfc8823).\nRFC 8823 extends ACME to allow end-users (such as individuals requesting S/MIME certificates for email) to obtain certificates, not just server operators. This enables new use cases like secure email and client authentication.\n\n## Prerequisites\n\n### Server Side\n\n- **ACME2Certifier** running version **0.39** or higher.\n- **Email Server**: The ACME2Certifier instance must be able to send and receive emails for the challenge/response flow required by RFC 8823. This typically means configuring access to an SMTP and IMAP server.\n\n### Client Side\n\n- **ACME Client with RFC 8823 Support**: You need an ACME client that implements the RFC 8823 extension for end-user certificate enrollment. For testing, we used the [acme_email test client](https://github.com/polhenarejos/acme_email) by Pol Henarejos.\n\n## How to Enable the Feature\n\n**Configure Email Support in `acme_srv.cfg`**\n\nAdd or update the following section in your configuration file:\n\n```cfg\n[Default]\nemail_identifier_support = true\nimap_server = imap.example.com\nimap_port = 993\nimap_use_ssl = true\nsmtp_server = smtp.example.com\nsmtp_port = 587\nsmtp_use_tls = true\nusername = acme2certifier\npassword = a2c-password\nemail = a2c@example.com\npolling_timer = 60\nconnection_timeout = 30\n...\n\n[Order]\nemail_identifier_support: True\nemail_identifier_rewrite: True\n```\n\nAdjust the values to match your email server settings.\n\n## Configuration Parameters\n\n| Parameter                | Description                                                                                   |\n|--------------------------|-----------------------------------------------------------------------------------------------|\n| `email_identifier_support` | Set to `true` to allow email identifiers for end-user certificates.                         |\n| `email_identifier_rewrite` | Set to `true` to send email addresses as part of DNS indentifiers. You need to enable this option when [acme_email test client](https://github.com/polhenarejos/acme_email) as the client is not fully rfc compliant.  [Further details](https://github.com/polhenarejos/acme_email/issues/4)                       |\n| `imap_server`            | IMAP server address for receiving challenge emails.                                          |\n| `imap_port`              | IMAP server port (usually 993 for SSL).                                                      |\n| `imap_use_ssl`           | Set to `true` to use SSL for IMAP connections.                                               |\n| `smtp_server`            | SMTP server address for sending challenge emails.                                            |\n| `smtp_port`              | SMTP server port (usually 587 for TLS).                                                      |\n| `smtp_use_tls`           | Set to `true` to use TLS for SMTP connections.                                               |\n| `username`               | acme2certifier username for authenticating to the email server.                              |\n| `password`               | acme2certifier password for authenticating to the email server.                              |\n| `email_address`          | email-address used by acme2certifier for sending and receiving emails.                       |\n| `polling_timer`          | Interval (in seconds) for polling the mailbox for challenge responses.                       |\n| `connection_timeout`     | Timeout (in seconds) for email server connections.                                           |\n\n______________________________________________________________________\n\n## Important Notes\n\n- **Implementation Status:**\n  The RFC 8823 feature is new and may be incomplete, as there are currently very few ACME clients supporting this extension.\n  If you encounter issues or unexpected behavior, please [open an issue](https://github.com/grindsa/acme2certifier/issues) with details about your setup and the problem.\n\n- **Client Compatibility:**\n  For testing, we recommend the [acme_email test client](https://github.com/polhenarejos/acme_email). CLI parameters for certificate enrollment can be taken from the respective [github action](../.github/actions/wf_specific/emailreply_challengevalidation/acme_email_enroll/action.yml#L53)\n  If you use another client, ensure it supports RFC 8823.\n\n- **Feedback Encouraged:**\n  Your feedback and bug reports are valuable to improve this feature and ensure interoperability with more clients.\n\n**Reference:**\n\n- [RFC 8823: End-User Certificate Enrollment for ACME](https://datatracker.ietf.org/doc/html/rfc8823)\n- [ACME Email S/MIME Client](https://github.com/polhenarejos/acme_email)\n"
  },
  {
    "path": "docs/tnauthlist.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title Support for TNAuthList Identifier and tkauth-01 Challenges -->\n\n# TNAuthList Support\n\nSupport for the **TNAuthList** identifier and **tkauth-01** challenges is currently **experimental**, as neither the identifier nor the challenge type has been fully standardized.\n\n## Implementation\n\nThe current implementation follows these specifications:\n\n- [RFC 9447 - Automated Certificate Management Environment (ACME) Challenges Using an Authority Token](https://www.rfc-editor.org/rfc/rfc9447)\n- [RFC 9448 - TNAuthList Profile of Automated Certificate Management Environment (ACME) Authority Token](https://www.rfc-editor.org/rfc/rfc9448.html)\n- [ATIS-1000080](https://access.atis.org/higherlogic/ws/public/download/69428)\n\n## Enabling TNAuthList Support\n\nBy default, TNAuthList support is **disabled**. To enable it, modify the **`Order`** section of the configuration file (`acme_srv.cfg`) and add:\n\n```ini\n[Order]\ntnauthlist_support: True\n```\n\n## ACME Client Support\n\nCurrently, **no ACME client** officially supports the TNAuthList extension. However, for testing purposes, I have added support to a modified version of **[acme.sh](https://github.com/grindsa/acme.sh)**. These changes have **not yet been merged** into the main repository.\n\nIf you choose to use this modified version, please proceed **at your own risk** and provide feedback.\n\n## Enrolling a Certificate with TNAuthList\n\nTo enroll a certificate that includes a **TNAuthList** certificate extension, use the following command:\n\n```sh\nacme.sh --server http://<server-name> --issue -d <fqdn>         --tnauth <TN Authorization List> --spctoken <Service Provider Code Token>         --standalone -w /tmp --debug 2 --output-insecure --force --log acme.log\n```\n"
  },
  {
    "path": "docs/trigger.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Trigger -->\n\n# `ca_handler.trigger()`\n\nThe `trigger` method allows a **CA server** to invoke specific actions on **acme2certifier**. These actions are defined by the respective **CA handler**.\n\nThis method is particularly useful in scenarios where a **CSR enters a pending state**, and the **CA server has the ability to trigger scripts** after CSR approval.\n\n## Triggering a Request\n\nThe CA server must send an **HTTP POST request** to the `/trigger` endpoint, including a **base64-encoded payload** in JSON format.\n\n### Example Request\n\n```bash\n# Modify to match your setup\nBASE64_PAYLOAD=$(echo \"Hello Payload\" | base64)\nACME2CERTIFIER_URL=\"http://10.97.149.146\"\n\n# Invoke curl\ncurl -X POST -H \"Content-Type: application/json\" -d \"{\"payload\":\"$BASE64_PAYLOAD\"}\" \"$ACME2CERTIFIER_URL/trigger\"\n```\n\n## Processing the Payload\n\n- The payload is **extracted** from the POST request.\n- It is **forwarded** to the `ca_handler.trigger()` method for further processing.\n\n## Expected Return Values\n\nThe `ca_handler.trigger()` method is expected to return:\n\n- **An error message** (if any).\n- **The certificate chain** in PEM format.\n- **The certificate** in ASN.1 (binary) format, **Base64-encoded** (needed for later revocation).\n\n## Database Update\n\nIf a **valid certificate** is returned, **acme2certifier** will:\n\n1. **Update the local database**.\n1. **Set the order resource status to \"valid\"**.\n1. **Establish correlation** between the certificate and certificate resource by comparing the public keys of the **certificate** and **CSR** (which should already exist in the database).\n"
  },
  {
    "path": "docs/upgrading.md",
    "content": "<!-- markdownlint-disable MD013 MD029 -->\n\n<!-- wiki-title Upgrading acme2certifier -->\n\n# Upgrading acme2certifier\n\n## Upgrade to Version 0.17\n\nIn **acme2certifier v0.17**, the `acme` module (which implements ACME server functionality) has been **renamed** to `acme_srv`.\n\nThis renaming was done to **avoid naming conflicts** with [acme-python](https://acme-python.readthedocs.io/en/stable/) and affects **acme2certifier deployments running as Django projects**, as the Django application must be renamed, and the **database schema** must be updated.\n\n### Automatic Upgrade for Container-Based Deployments\n\nIf you are using the **prebuilt Django containers** running on **Apache2** or **NGINX**, the necessary modifications will be **applied automatically** when deploying the updated containers:\n\n[acme2certifier Django Containers](https://hub.docker.com/repository/docker/grindsa/acme2certifier/)\n\n### Manual Upgrade for Custom Django Deployments\n\nIf you installed **acme2certifier** manually as a **Django project**, follow these steps:\n\n### 1. Download and Extract the v0.17 Archive\n\n```bash\ncd /var/www/acme2certifier\nwget <new_version_url> -O acme2certifier-0.17.tar.gz\ntar -xzf acme2certifier-0.17.tar.gz\n```\n\n### 2. Install `django-rename-app`\n\n```bash\npip install django-rename-app\n```\n\n### 3. Modify `settings.py`\n\nEdit your **Django settings** file (usually found at `/var/www/acme2certifier/acme2certifier/settings.py`) and rename the existing `acme` app to `acme_srv`:\n\n```python\nINSTALLED_APPS = [\n    ...\n    'acme_srv',\n    ...\n]\n```\n\n### 4. Rename the App\n\n```bash\npython manage.py rename_app acme acme_srv\n```\n\n### 5. Update Configuration and Handlers\n\n```bash\ncp acme/acme_srv.cfg acme_srv/acme_srv.cfg\ncp examples/db_handler/django_handler.py acme_srv/db_handler.py\n\n# If there is no `handler_file` parameter in `acme_srv.cfg`, copy your CA handler\ncp examples/ca_handler/* acme_srv/\n```\n\n### 6. Start acme2certifier and Verify\n\n```bash\nsystemctl restart acme2certifier\ncurl http[s]://<acme-srv>/directory\n```\n\n### 7. Cleanup\n\nOnce the upgrade is verified, remove the old `acme` directory:\n\n```bash\nrm -rf acme\n```\n\nYour acme2certifier instance is now successfully upgraded to v0.17! 🚀\n"
  },
  {
    "path": "docs/vault.md",
    "content": "<!-- markdownlint-disable MD013 MD014 MD029 -->\n\n<!-- Hashicorp Vault PKI CA-handler -->\n\n# Hashicorp Vault PKI CA-handler\n\n## Overview\n\nThis CA-handler adds support for certificate management using [Hashicorp Vault's PKI secrets engine](https://developer.hashicorp.com/vault/docs/secrets/pki). It provides methods integrating Vault as a backend for enrollment and revocation operations.\n\n## Prerequisites\n\n- **Hashicorp Vault** installed and running with the PKI secrets engine enabled.\n- Vault server must be initialized, unsealed, and accessible from acme2certifier.\n- The following Vault configuration items must be set up:\n  - PKI secrets engine enabled at the desired path (e.g., `pki` or `pki_int`)\n  - Roles and issuers configured for your use case\n  - API access token for Vault with sufficient permissions\n  - vault PKI needs to return the entire certificate chain (up-to rootca) in its response to an enrollment request.\n\n## Configuration\n\nAdd a `[CAhandler]` section to your configuration file (e.g., `acme_srv.cfg`):\n\n```ini\n[CAhandler]\nvault_url = http://vault-server:8200\nvault_path = <pki path>\nvault_role = <vault-role>\nvault_token = <your-vault-token>\nissuer_ref = <issuer-id>         # Optional\nrequest_timeout = 20             # Optional, default is 20 seconds\ncert_validity_days = 365         # Optional, default is 365 days\nca_bundle = <path>               # CA bundle to verify the certificate presented by Vault server\n```\n\nOther configuration options (domain lists, profiles, proxies, etc.) are loaded as in previous handlers.\n\n## Passing a vault-role from client to server\n\nacme2certifier supports the the [Automated Certificate Management Environment (ACME) Profiles Extension draft](acme_profiling.md) allowing an acme-client to specify a `vault-role` parameter to be submitted to the CA server.\n\nThe list of supported profiles must be configured in `acme_srv.cfg`\n\n```config\n[Order]\nprofiles: {\"vault-role1\": \"http://foo.bar/vault-role1\", \"vault-role2\": \"http://foo.bar/vault-role2\", \"vault-role3\": \"http://foo.bar/vault-role3\"}\n```\n\nOnce enabled, a client can specify the cert_profile_name to be used as part of an order request. Below an example for lego:\n\n```bash\ndocker run -i -v $PWD/lego:/.lego/ --rm --name lego goacme/lego --tls-skip-verify -s https://<acme-srv> -a --email \"lego@example.com\" -d <fqdn> --http run --profile vault-role1\n```\n\n## eab profiling\n\nThis handler can use the [eab profiling feature](eab_profiling.md) to allow individual enrollment configuration per acme-account as well as restriction of CN and SANs to be submitted within the CSR. The feature is disabled by default and must be activatedd in `acme_srv.cfg`\n\n```cfg\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\nBelow is an example key file used during regression testing:\n\n- ACME clients using `keyid_00` can submit vault-role parameters \"clientauth\" or \"serverauth\" as part of an enrollmnet request (see above section)\n- ACME clients using `keyid_01` enroll certificates from a different CA (pki-path `pki_alternate`) with the vault-role `vault-alternate`\n- ACME clients using `keyid_03` are using a specific list of allowed domains\n- ACME clients using `keyid_04` are using the paramters configured in `acme_srv_cfg`\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"vault-role\": [\"clientauth\", \"serverauth\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"]\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"vault-role\": \"clientauth_alternate\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"ca_name\": \"pki_alternate\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n\n## Notes\n\n- Ensure your Vault token has permissions for PKI operations.\n- the [Build your own certificate authority (CA) tutorial](https://developer.hashicorp.com/vault/tutorials/pki/pki-engine) has been used to setup a vault test system. The respective configuration can be found in the [test-workflow](https://github.com/grindsa/acme2certifier/blob/master/.github/actions/wf_specific/vault_ca_handler/vault_prep/action.yml#L109)\n"
  },
  {
    "path": "docs/xca.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title CA Handler for XCA -->\n\n# Support for XCA-Based Certificate Authorities\n\nThis handler allows **acme2certifier** to store **certificates** and **requests** in an [XCA](https://github.com/chris2511/xca/) SQLite database.\n\nIt also supports fetching **enrollment templates** from XCA and applying them to **certificate signing requests (CSRs)**.\n\n## Prerequisites\n\nTo use this handler, you need:\n\n- A **preconfigured XCA database** with **CA certificates** and **keys** imported.\n- The **Internal Name** of the Certificate Authority, as shown in the XCA application.\n\n![xca-ca-list](xca-ca-list.png)\n\n## Configuration\n\n### 1. Copy the CA Handler to the acme2certifier Directory\n\n```bash\ncp example/ca_handlers/xca_ca_handler.py acme_srv/ca_handler.py\n```\n\n### 2. Ensure Database Accessibility\n\n- Place the **XCA database** in a directory accessible to **acme2certifier**.\n- Set ownership to the user running the web services.\n- Restrict permissions to prevent unauthorized access.\n\n### 3. Modify the Server Configuration\n\nEdit the **server configuration** (`/acme_srv/acme_srv.cfg`) and add the following parameters:\n\n```ini\n[CAhandler]\nhandler_file: examples/ca_handler/xca_ca_handler.py\nxdb_file: acme_srv/xca/acme2certifier.xdb\nxdb_permission: 600\nissuing_ca_name: sub-ca\nissuing_ca_key: sub-ca-key\npassphrase_variable: XCA_PASSPHRASE\nca_cert_chain_list: [\"root-ca\"]\ntemplate_name: XCA template to be applied to CSRs\n```\n\n### Parameter Explanations\n\n- **xdb_file** – Path to the **XCA database**.\n- **xdb_permission** *(optional)* – **File permissions** for the XCA database (default: `660`).\n- **issuing_ca_name** – **XCA name** of the CA used for certificate issuance.\n- **issuing_ca_key** – **XCA name** of the key used to sign certificates. If not set, it defaults to the value in `issuing_ca_name`.\n- **passphrase_variable** *(optional)* – Environment variable containing the **passphrase** to decrypt the CA key (overridden if `passphrase` is set).\n- **passphrase** *(optional)* – **Passphrase** to access the database and decrypt the private CA key.\n- **ca_cert_chain_list** *(optional)* – List of **root and intermediate CA certificates** to be included in the bundle returned to an ACME client (**do not include the issuing CA certificate**).\n- **template_name** *(optional)* – Name of the **XCA template** to be applied during certificate issuance.\n- **allowed_domainlist** *(optional)* – List of allowed **domain names** for enrollment (JSON format). Example: `[\"bar.local\", \"bar.foo.local\"]` (default: `[]`).\n- **enrollment_config_log** *(optional)* – Enable logging of enrollment parameters (default: `False`).\n- **enrollment_config_log_skip_list** *(optional)* – List of **enrollment parameters** to exclude from logs (JSON format). Example: `[\"parameter1\", \"parameter2\"]` (default: `[]`).\n\n## Template Support\n\n**Template support was introduced in v0.13** and applies the following parameters during certificate issuance:\n\n- **Certificate validity** (`validN`/`validM`)\n- **Basic Constraints** (`ca`)\n- **Key Usage Attributes** (`keyUse`) – Defaults to:\n  `digitalSignature, nonRepudiation, keyEncipherment, keyAgreement` if not specified.\n- **Extended Key Usage Attributes** (`eKeyUse`)\n- **CRL Distribution Points** (`crlDist`)\n- **Enforcement of DN Attributes:**\n  - **OU**: Organizational Unit\n  - **O**: Organization\n  - **L**: Locality\n  - **S**: State or Province Name\n  - **C**: Country Name\n\n## Enabling EAB Profiling\n\nThis handler supports the **EAB profiling feature**, which allows:\n\n- **Custom enrollment configurations per ACME account**.\n- **Restrictions on CN and SANs in the CSR**.\n\nTo enable **EAB profiling**, modify `acme_srv.cfg`:\n\n```ini\n[EABhandler]\neab_handler_file: examples/eab_handler/kid_profile_handler.py\nkey_file: <profile_file>\neab_profiling: True\n\n[CAhandler]\n...\n```\n\n### Example Key File (Used in Regression Testing)\n\n```json\n{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"template_name\": [\"template\", \"acme\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"unknown_key\": \"unknown_value\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"template_name\": \"template\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.acme\"],\n      \"issuing_ca_name\": \"root-ca\",\n      \"issuing_ca_key\": \"root-ca\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n  }\n}\n```\n\n## Final Notes\n\nEnjoy enrolling and revoking certificates! 🚀\n"
  },
  {
    "path": "examples/Docker/.env",
    "content": "COMPOSE_PROJECT_NAME=acme2certifier\n### CONTEXT can be \"wsgi\" or \"django\"\nCONTEXT=wsgi\n### WEBSERVER can be \"nginx\" or \"apache2\"\nWEBSERVER=apache2\n"
  },
  {
    "path": "examples/Docker/.gitignore",
    "content": "# Local changes to docker compose can be placed here\ndocker-compose.override.yaml\n# Configuration for the containers are held here\ndata/\n"
  },
  {
    "path": "examples/Docker/README.md",
    "content": "<!-- markdownlint-disable MD013 -->\n\n<!-- wiki-title Containerized Installation Using Apache2 or Nginx as Web Server with WSGI or Django -->\n\n# Containerized Installation Using Apache2 or Nginx as Web Server with WSGI or Django\n\nThis is the **fastest and most convenient** way to deploy **acme2certifier**. After installation, **acme2certifier** will run inside a **minimal Ubuntu 24.04 container**, using either **Apache2** or **Nginx** as the web server.\n\n## Persistent Storage\n\n**acme2certifier** requires persistent storage for:\n\n- **Configuration File:** `acme_srv.cfg`\n- **Customized CA Handlers or runtime data (files and directories) belonging to CA handlers:** `ca_handler.py`\n- **Database:** `acme_srv.db` (in case of WSGI installations)\n- **Django migration sets** (in case of Django based deployments)\n\nBy default, these files are stored in the **`data/`** folder and mounted inside the container at:\n\n```plaintext\n/var/www/acme2certifier/volume\n```\n\nThe **data folder path** can be modified in [`docker-compose.yml`](https://github.com/grindsa/acme2certifier/blob/master/examples/Docker/docker-compose.yml) to match your setup.\n\n## Ports\n\nBy default, **acme2certifier** exposes its web services on the following ports **inside the container**:\n\n- **HTTP:** Port **80**\n- **HTTPS:** Port **443** (optional, enabled if certificate and key are present)\n\nYou can map these internal ports to any available ports on your host system using Docker’s port mapping. For example, in `docker-compose.yml`:\n\n```yaml\nports:\n  - \"22280:80\"   # Maps host port 22280 to container port 80 (HTTP)\n  - \"22443:443\"  # Maps host port 22443 to container port 443 (HTTPS)\n```\n\nYou may also use the default ports:\n\n```yaml\nports:\n  - \"80:80\"\n  - \"443:443\"\n```\n\n**Note:**\n\n- The container does **not** expose ports 22280 or 22443 internally; these are just example host ports for mapping.\n- HTTPS (port 443) will only be available if both `acme2certifier_cert.pem` and `acme2certifier_key.pem` are present in `/var/www/acme2certifier/volume`.\n\n## Configuration via `.env`\n\nThe `.env` file allows customization, including:\n\n- **Branch Selection:** `master` or `devel`\n- **Context:** `wsgi` or `django`\n- **Web Server:** `apache2` or `nginx`\n\nExample `.env` file:\n\n```ini\nCOMPOSE_PROJECT_NAME=acme2certifier\nBRANCH=master\nCONTEXT=wsgi\nWEBSERVER=apache2\n```\n\n______________________________________________________________________\n\n## Building the Docker Image\n\n```bash\ncd ~/acme2certifier/examples/Docker\ndocker-compose build --no-cache\n```\n\nExpected output:\n\n```bash\nBuilding srv\nStep 1/17 : FROM ubuntu:24.04\n ---> 1d622ef86b13\nStep 2/17 : LABEL maintainer=\"grindelsack@gmail.com\"\n ---> Running in 03f043052bc9\nRemoving intermediate container 03f043052bc9\n...\n```\n\n______________________________________________________________________\n\n## Setting the Timezone\n\nContainers default to **UTC**, which can make log correlation difficult. To set a custom timezone, create a `docker-compose.override.yml` file:\n\n```yaml\nversion: '3.2'\nservices:\n  acme-srv:\n    environment:\n      TZ: \"Your/Timezone\"\n```\n\n[List of Timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)\n\n______________________________________________________________________\n\n## Starting acme2certifier\n\n```bash\ndocker-compose up -d\n```\n\nIf you modify `.env`, rebuild the image:\n\n```bash\ndocker-compose build --no-cache\n```\n\nDuring startup, the **entry-point script** checks for missing configuration files in `data/`:\n\n- **Configuration file:** [`acme_srv.cfg`](../../examples/acme_srv.cfg)\n- **Stub handler:** [`skeleton_ca_handler.py`](../../examples/ca_handler/skeleton_ca_handler.py)\n\nFor **Django-based deployments**, a **project-specific `settings.py`** will also be created in `data/`.\n\n______________________________________________________________________\n\n## Verifying the Container\n\nCheck if the container is running:\n\n```bash\ndocker-compose ps\n```\n\nExpected output:\n\n```plaintext\n        Name                      Command               State                       Ports\n-------------------------------------------------------------------------------------------------------------\nacme2certifier_srv_1   /docker-entrypoint.sh /usr ...   Up      0.0.0.0:22443->443/tcp, 0.0.0.0:22280->80/tcp\n```\n\nTest the **ACME directory endpoint**:\n\n```bash\ndocker run -it --rm --network acme curlimages/curl http://acme-srv/directory | python -m json.tool\n```\n\nExpected output:\n\n```json\n{\n    \"6a01d6abe3a84de2831d24aa5451b3a2\": \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\",\n    \"keyChange\": \"http://acme2certifier_srv_1/acme_srv/key-change\",\n    \"meta\": {\n        \"author\": \"grindsa <grindelsack@gmail.com>\",\n        \"home\": \"https://github.com/grindsa/acme2certifier\",\n        \"name\": \"acme2certifier\",\n        \"version\": \"0.9-dev\"\n    },\n    \"newAccount\": \"http://acme2certifier_srv_1/acme_srv/newaccount\",\n    \"newAuthz\": \"http://acme2certifier_srv_1/acme_srv/new-authz\",\n    \"newNonce\": \"http://acme2certifier_srv_1/acme_srv/newnonce\",\n    \"newOrder\": \"http://acme2certifier_srv_1/acme_srv/neworders\",\n    \"revokeCert\": \"http://acme2certifier_srv_1/acme_srv/revokecert\"\n}\n```\n\n### Restarting the Container\n\nIf you modify `acme_srv.cfg`, `ca_handler.py`, or `settings.py`, restart the container:\n\n```bash\ndocker-compose restart\n```\n\n______________________________________________________________________\n\n## Enrolling a Certificate\n\nUse your preferred **ACME client**. If enrollment fails:\n\n1. **Check the CA handler configuration.**\n1. **Review logs.**\n1. **Enable [debug mode](../../docs/acme_srv.md) in acme2certifier.**\n\n______________________________________________________________________\n\n## Enabling TLS (Apache2)\n\nTo enable **TLS support**, place `acme2certifier.pem` in the volume. It must contain:\n\n- **Private key**\n- **End-entity certificate**\n- **Intermediate CA certificates** (from **leaf to root**; do **not** include the root CA)\n\nExample:\n\n```pem\n-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nEnd-entity certificate data\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nIntermediate CA certificate(s)\n-----END CERTIFICATE-----\n```\n\n______________________________________________________________________\n\n## Enabling TLS (Nginx)\n\nFor **Nginx**, place the following files in the volume:\n\n- **`acme2certifier_cert.pem`** – Certificate file\n- **`acme2certifier_key.pem`** – Private key\n\nBoth must be in **PEM format**.\n\n______________________________________________________________________\n\n## Running acme2certifier Without Docker-Compose\n\nYou can run the **container manually** with:\n\n```bash\ndocker run -d -p 22280:80 -p 22443:443 --rm --name=a2c-srv   -v \"/home/grindsa/docker/a2c/data\":/var/www/acme2certifier/volume/   grindsa/acme2certifier:apache2-wsgi\n```\n\nThis will:\n\n- **Map internal port 80** to **external port 22280**.\n- **Map internal port 443** to **external port 22443**.\n- **Mount the `data/` directory** for persistent storage.\n\n______________________________________________________________________\n\n### 🎉 Congratulations! acme2certifier is now running in a containerized environment! 🚀\n"
  },
  {
    "path": "examples/Docker/almalinux-systemd/Dockerfile",
    "content": "FROM almalinux:9\nENV container docker\n\nWORKDIR /lib/systemd/system/sysinit.target.wants/\nRUN for i in ; do [ \"$i\" == systemd-tmpfiles-setup.service ] || rm -f \"$i\"; done && \\\n    rm -rf /lib/systemd/system/multi-user.target.wants/ && \\\n    rm -rf /etc/systemd/system/.wants/ && \\\n    rm -rf /lib/systemd/system/local-fs.target.wants/ && \\\n    rm -f /lib/systemd/system/sockets.target.wants/udev && \\\n    rm -f /lib/systemd/system/sockets.target.wants/initctl && \\\n    rm -rf /lib/systemd/system/basic.target.wants/ && \\\n    rm -f /lib/systemd/system/anaconda.target.wants/*\n\n# VOLUME [ “/sys/fs/cgroup” ]\nCMD [\"/usr/sbin/init\"]\n"
  },
  {
    "path": "examples/Docker/almalinux-systemd/django_tester.sh",
    "content": "#!/bin/bash\n\ncase \"${1}\" in\n\n  \"update\")\n    echo \"update configuration only\"\n    # yes | cp /tmp/acme2certifier/acme_srv.cfg /opt/acme2certifier/acme_srv\n    yes | cp -R /tmp/acme2certifier/volume/acme_ca/* /opt/acme2certifier/volume/acme_ca/\n    ;;\n\n  \"restart\")\n    echo \"update configuration and restart service\"\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /opt/acme2certifier/acme_srv\n    #if [[ -d /tmp/acme2certifier/acme_ca ]]; then\n    #  yes | cp -R /tmp/acme2certifier/acme_ca/* /opt/acme2certifier/volume/acme_ca/\n    #fi\n    if [[ -d /tmp/acme2certifier/volume ]]\n      then\n      echo \"copying volume\"\n      mkdir -p /opt/acme2certifier/volume\n      yes | cp -R /tmp/acme2certifier/volume/* /opt/acme2certifier/volume/\n    fi\n    systemctl restart acme2certifier.service\n    systemctl restart nginx.service\n    ;;\n\n  *)\n    echo \"install missing packages\"\n    yum -y install epel-release\n    yum install -y procps syslog-ng\n    systemctl start syslog-ng.service\n\n    yum -y localinstall /tmp/acme2certifier/*.rpm\n\n\n    if [[ -f /tmp/acme2certifier/packages-microsoft-prod.rpm ]]\n      then\n      echo \"install Microsoft repository configuration package\"\n      yum -y localinstall /tmp/acme2certifier/packages-microsoft-prod.rpm\n      ACCEPT_EULA=Y yum install -y msodbcsql18 python3-pip python3-pyodbc\n      if [[ -f /usr/bin/pip3 ]]\n        then\n        echo \"installing MSSQL Django dependencies with pip3 and pinning mssql-django to 1.3\"\n        yum -y install gcc gcc-c++ python3-devel unixODBC-devel\n        pip3 install mssql-django==1.3 # pyodbc\n        else\n        echo \"installing MSSQL Django dependencies with pip\"\n        pip install mssql-django # pyodbc\n      fi\n      # yum install -y unixODBC\n      else\n      yum -y install python3-PyMySQL python3-sqlparse python3-psycopg2 python3-pyyaml python3-mysqlclient\n    fi\n\n    yes | cp /opt/acme2certifier/examples/db_handler/django_handler.py /opt/acme2certifier/acme_srv/db_handler.py\n    yes | cp -R /opt/acme2certifier/examples/django/* /opt/acme2certifier/\n\n    cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d\n    cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d\n    mkdir -p /opt/acme2certifier/volume/\n\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /opt/acme2certifier/acme_srv\n    if [[ -d /tmp/acme2certifier/volume ]]\n      then\n      mkdir -p /opt/acme2certifier/volume\n      yes | cp -R /tmp/acme2certifier/volume/* /opt/acme2certifier/volume/\n    fi\n    if [[ -d /tmp/acme2certifier/acme2certifier ]]\n      then\n      mkdir -p /opt/acme2certifier/acme2certifier\n      yes | cp -R /tmp/acme2certifier/acme2certifier/* /opt/acme2certifier/acme2certifier/\n    fi\n    if [[ -d /tmp/acme2certifier/nginx ]]\n      then\n      yes | cp -R /tmp/acme2certifier/nginx/* /etc/nginx/\n    fi\n\n    cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig\n    head -n 37 /etc/nginx/nginx.conf.orig > /etc/nginx/nginx.conf\n    echo \"}\" >> /etc/nginx/nginx.conf\n\n    cd /opt/acme2certifier\n    python3 manage.py makemigrations\n    python3 manage.py migrate\n    python3 /opt/acme2certifier/tools/django_update.py\n    python3 manage.py loaddata acme_srv/fixture/status.yaml\n\n    chown -R nginx.nginx /opt/acme2certifier/acme2certifier/\n    chown -R nginx.nginx /opt/acme2certifier/volume/\n\n    systemctl enable acme2certifier.service\n    systemctl start acme2certifier.service\n\n    systemctl enable nginx.service\n    systemctl start nginx.service\n    ;;\nesac\n"
  },
  {
    "path": "examples/Docker/almalinux-systemd/rpm_tester.sh",
    "content": "#!/bin/bash\n\ncase \"${1}\" in\n\n  \"update\")\n    echo \"update configuration only\"\n    # yes | cp /tmp/acme2certifier/acme_srv.cfg /opt/acme2certifier/acme_srv\n    yes | cp -R /tmp/acme2certifier/volume/acme_ca/* /opt/acme2certifier/volume/acme_ca/\n    ;;\n\n  \"restart\")\n    echo \"update configuration and restart service\"\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /opt/acme2certifier/acme_srv\n    if [[ -d /tmp/acme2certifier/volume ]]\n      then\n      echo \"copying volume\"\n      mkdir -p /opt/acme2certifier/volume\n      yes | cp -R /tmp/acme2certifier/volume/* /opt/acme2certifier/volume/\n    fi\n    systemctl restart acme2certifier.service\n    systemctl restart nginx.service\n    ;;\n\n  *)\n    echo \"install missing packages\"\n    yum -y install epel-release\n    yum install -y procps syslog-ng\n    systemctl start syslog-ng.service\n\n    yum -y localinstall /tmp/acme2certifier/*.rpm\n    cp /opt/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d\n    cp /opt/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d\n    mkdir -p /opt/acme2certifier/volume/\n\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /opt/acme2certifier/acme_srv\n    if [[ -d /tmp/acme2certifier/volume ]]\n      then\n      mkdir -p /opt/acme2certifier/volume\n      yes | cp -R /tmp/acme2certifier/volume/* /opt/acme2certifier/volume/\n    fi\n\n    if [[ -d /tmp/acme2certifier/nginx ]]\n      then\n      mkdir -p /etc/nginx\n      yes | cp -R /tmp/acme2certifier/nginx/* /etc/nginx/\n    fi\n\n    cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig\n    head -n 37 /etc/nginx/nginx.conf.orig > /etc/nginx/nginx.conf\n    echo \"}\" >> /etc/nginx/nginx.conf\n\n    chown -R nginx.nginx /opt/acme2certifier/volume/\n    ls -la /opt/acme2certifier/\n    ls -la /opt/acme2certifier/volume\n\n    systemctl enable acme2certifier.service\n    systemctl start acme2certifier.service\n\n    systemctl enable nginx.service\n    systemctl start nginx.service\n    ;;\nesac\n"
  },
  {
    "path": "examples/Docker/almalinux-systemd/script_tester.sh",
    "content": "#!/bin/bash\n\necho \"install missing packages\"\nyum -y install epel-release\nyum install -y sudo checkpolicy python3-pip procps syslog-ng\nsystemctl start syslog-ng\n\ncd /tmp/acme2certifier\n\necho \"execute install script\"\nsh examples/install_scripts/a2c-centos9-nginx.sh\n\n\necho \"configure handler\"\nmkdir -p /opt/acme2certifier/volume/acme_ca/certs/\ncp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /opt/acme2certifier/volume/acme_ca/\n\necho \"fix ownership\"\nchown -R nginx /opt/acme2certifier/volume\n"
  },
  {
    "path": "examples/Docker/apache2/django/Dockerfile",
    "content": "FROM ubuntu:24.04\nLABEL maintainer=\"grindelsack@gmail.com\"\n\nENV APACHE_RUN_USER=www-data\nENV APACHE_RUN_GROUP=www-data\nENV APACHE_LOG_DIR=/var/log/apache2\n\nRUN apt-get update  && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install -y --no-install-recommends -y tzdata && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n    apache2 \\\n    apache2-data \\\n    curl \\\n    krb5-user \\\n    libapache2-mod-wsgi-py3 \\\n    libgssapi-krb5-2 \\\n    libkrb5-3 \\\n    python3-django \\\n    python3-gssapi \\\n    python3-mysqldb \\\n    python3-pip \\\n    python3-psycopg2 \\\n    python3-pymysql \\\n    python3-yaml && \\\n    curl https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb --output /tmp/packages-microsoft-prod.deb && \\\n    dpkg -i /tmp/packages-microsoft-prod.deb && \\\n    rm /tmp/packages-microsoft-prod.deb && \\\n    apt-get update && \\\n    ACCEPT_EULA=Y apt-get install -y msodbcsql18 && \\\n    apt-get clean &&  \\\n    rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* && \\\n    mkdir -p /var/www/acme2certifier/volume && \\\n    mkdir -p /var/www/acme2certifier/examples /var/www/acme2certifier/examples/\n\nCOPY ./ /var/www/acme2certifier/\n\n# configure acme2certifier\nRUN pip3 install impacket --break-system-packages && \\\n    rm -rf /usr/local/bin/* && \\\n    rm -rf /usr/local/lib/python3.12/dist-packages/impacket/examples/* && \\\n    pip3 install -r /var/www/acme2certifier/requirements.txt --break-system-packages && \\\n    pip3 install --no-cache-dir mssql-django pyodbc --break-system-packages --break-system-packages && \\\n    cp /var/www/acme2certifier/examples/apache2/apache_django.conf /etc/apache2/sites-enabled/acme2certifier.conf && \\\n    cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/  && \\\n    cp /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py  && \\\n    rm /var/www/acme2certifier/setup.py && \\\n    rm /var/www/acme2certifier/requirements.txt && \\\n    cp /var/www/acme2certifier/examples/Docker/apache2/django/docker-entrypoint.sh /docker-entrypoint.sh && \\\n    rm -rf /var/www/acme2certifier/examples/Docker && \\\n    rm -rf /var/www/acme2certifier/examples/db_handler && \\\n    rm -rf /var/www/acme2certifier/examples/nginx && \\\n    rm -rf /var/www/acme2certifier/examples/acme_srv.db.example && \\\n    rm -rf /var/www/acme2certifier/examples/acme2certifier_wsgi.py  && \\\n    rm /var/www/acme2certifier/acme2certifier/settings.py && \\\n    chown -R www-data:www-data /var/www/acme2certifier/ && \\\n    sed -i \"s/default = default_sect/\\default = default_sect\\nlegacy = legacy_sect/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\[default_sect\\]/\\[default_sect\\]\\nactivate = 1\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\${APACHE_LOG_DIR}\\/error.log/\\/dev\\/stderr/g\" /etc/apache2/apache2.conf && \\\n    a2enmod ssl && \\\n    rm /etc/apache2/sites-enabled/000-default.conf && \\\n    chmod a+rx /docker-entrypoint.sh  # NOSONAR\n\nWORKDIR /var/www/acme2certifier/\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"/usr/sbin/apache2ctl\", \"-D\", \"FOREGROUND\"]\n\nEXPOSE 80 443\n"
  },
  {
    "path": "examples/Docker/apache2/django/docker-entrypoint.sh",
    "content": "#!/bin/bash\n\n# create acme-srv.cfg if not existing\nif [[ ! -f /var/www/acme2certifier/volume/acme_srv.cfg ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/acme_srv.cfg /var/www/acme2certifier/volume/\nfi\n\n# enable tls if acme2certifier.pem exists on volume\nif [[ -f /var/www/acme2certifier/volume/acme2certifier.pem ]]\nthen\n    echo \"found acme2certifier.pem! enable TLS\" >> /proc/1/fd/1\n    cp  /var/www/acme2certifier/examples/apache2/apache_django_ssl.conf /etc/apache2/sites-enabled/acme2certifier_ssl.conf\nfi\n\n# create ca_handler if:\n# - ca_handler.py does not exists in volume AND\n# - no entry handler_file: exists in acme_srv.cfg\n# - define ca_handler defined under handler_file does not exists\nif ( [[ ! -f /var/www/acme2certifier/volume/ca_handler.py ]] && \\\n     ! ( grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg &> /dev/null && \\\n         [[ -f $(grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg | awk -F\":\" '{print $2}') ]] \\\n        ))\nthen\n    echo \"no ca_handler.py found! creating from skeleton_ca_handler.py\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/ca_handler/skeleton_ca_handler.py /var/www/acme2certifier/volume/ca_handler.py\nelse\n    if [[ -f /var/www/acme2certifier/volume/ca_handler.py ]]\n    then\n        sed -i \"s/from acme.helper import/from acme_srv.helper import/g\" /var/www/acme2certifier/volume/ca_handler.py\n    fi\nfi\n\n# create symlink for the acme_srv.cfg\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    chown www-data.www-data /var/www/acme2certifier/volume/acme_srv.cfg\nfi\n\n# create symlink for the acme_srv.db\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.db ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.db /var/www/acme2certifier/acme_srv/acme_srv.db\nfi\n\n# create symlink for the ca_handler\nif [[ ! -L /var/www/acme2certifier/acme_srv/ca_handler.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/ca_handler.py /var/www/acme2certifier/acme_srv/ca_handler.py\nfi\n\n# create settings.py if not existing\nif [[ ! -f /var/www/acme2certifier/volume/settings.py ]]\nthen\n    echo \"no settings.py found! copy settings.py\"  >> /proc/1/fd/1\n    egrep -v '(# SECURITY WARNING: keep the secret key used in production secret!|^SECRET_KEY)' /var/www/acme2certifier/examples/django/acme2certifier/settings.py > /var/www/acme2certifier/volume/settings.py\n    ## generate SECRET_KEY\n    echo \"generating SECRET_KEY\" >> /proc/1/fd/1\n    DJANGO_SECRET_KEY=$(python3 tools/django_secret_keygen.py)\n    cat >>/var/www/acme2certifier/volume/settings.py <<EOF\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '${DJANGO_SECRET_KEY}'\nEOF\n    echo \"adding '*' wildcard hosts in settings.py\"  >> /proc/1/fd/1\n    sed -i \"s/ALLOWED_HOSTS = \\['127.0.0.1'\\]/ALLOWED_HOSTS = \\['127.0.0.1','*'\\]/g\" /var/www/acme2certifier/volume/settings.py\nfi\n\n# create migrations if not existing\nif [[ ! -d /var/www/acme2certifier/volume/migrations ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp  -R /var/www/acme2certifier/examples/django/acme_srv/migrations /var/www/acme2certifier/volume/\n    # mkdir -p /var/www/acme2certifier/volume/migrations\nfi\n\n# create a symlink for migrations\nif [[ ! -L /var/www/acme2certifier/acme_srv/migrations ]]\nthen\n    if [[ -d /var/www/acme2certifier/volume/migrations ]]\n    then\n        echo \"delete migration directory\" >> /proc/1/fd/1\n        rm -rf /var/www/acme2certifier/acme_srv/migrations\n    fi\n\n    echo \"create symlink for migration directory\" >> /proc/1/fd/1\n    ln -s /var/www/acme2certifier/volume/migrations /var/www/acme2certifier/acme_srv/\nfi\n\n# create a symlink for settings.py\nif [[ ! -L /var/www/acme2certifier/acme2certifier/settings.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/settings.py /var/www/acme2certifier/acme2certifier/settings.py\nfi\n\n# check if we need to remove django_rename app\nif ( grep \"    'django_rename_app',\" /var/www/acme2certifier/volume/settings.py &> /dev/null)\nthen\n    echo \"remove django_rename application\" >> /proc/1/fd/1\n    sed -i \"/    'django_rename_app',/d\" /var/www/acme2certifier/volume/settings.py\nfi\n\necho \"apply migrations\"  >> /proc/1/fd/1\ntouch /var/www/acme2certifier/acme_srv/migrations/__init__.py\npython3 /var/www/acme2certifier/tools/django_update.py\npython3 manage.py loaddata acme_srv/fixture/status.yaml\n\nchown -R www-data /var/www/acme2certifier/volume\nchmod u+s /var/www/acme2certifier/volume/\n\nexec \"$@\"\n"
  },
  {
    "path": "examples/Docker/apache2/wsgi/Dockerfile",
    "content": "FROM ubuntu:24.04\nLABEL maintainer=\"grindelsack@gmail.com\"\n\nENV APACHE_RUN_USER=www-data\nENV APACHE_RUN_GROUP=www-data\nENV APACHE_LOG_DIR=/var/log/apache2\n\nRUN apt-get update  && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get -y install --no-install-recommends tzdata && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n    apache2 \\\n    apache2-data \\\n    curl \\\n    krb5-user \\\n    libapache2-mod-wsgi-py3 \\\n    libgssapi-krb5-2 \\\n    libkrb5-3 \\\n    python3-gssapi \\\n    python3-pip && \\\n    rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* && \\\n    mkdir -p /var/www/acme2certifier/volume && \\\n    mkdir -p /var/www/acme2certifier/examples /var/www/acme2certifier/examples/\n\nCOPY ./ /var/www/acme2certifier/\n\n# configure acme2certifier\nRUN pip3 install impacket --break-system-packages && \\\n    rm -rf /usr/local/bin/* && \\\n    rm -rf /usr/local/lib/python3.12/dist-packages/impacket/examples/* && \\\n    pip3 install -r /var/www/acme2certifier/requirements.txt --break-system-packages && \\\n    cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-enabled/acme2certifier.conf  && \\\n    cp /var/www/acme2certifier/examples/acme2certifier_wsgi.py /var/www/acme2certifier/acme2certifier_wsgi.py  && \\\n    cp /var/www/acme2certifier/examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py  && \\\n    rm /var/www/acme2certifier/setup.py && \\\n    rm /var/www/acme2certifier/requirements.txt && \\\n    cp /var/www/acme2certifier/examples/Docker/apache2/wsgi/docker-entrypoint.sh /docker-entrypoint.sh && \\\n    rm -rf /var/www/acme2certifier/examples/Docker && \\\n    rm -rf /var/www/acme2certifier/examples/django && \\\n    rm -rf /var/www/acme2certifier/examples/db_handler && \\\n    rm -rf /var/www/acme2certifier/examples/nginx && \\\n    rm -rf /var/www/acme2certifier/examples/acme_srv.db.example && \\\n    rm -rf /var/www/acme2certifier/examples/acme2certifier_wsgi.py  && \\\n    chown -R www-data:www-data /var/www/acme2certifier/ && \\\n    sed -i \"s/default = default_sect/\\default = default_sect\\nlegacy = legacy_sect/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\[default_sect\\]/\\[default_sect\\]\\nactivate = 1\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\${APACHE_LOG_DIR}\\/error.log/\\/dev\\/stderr/g\" /etc/apache2/apache2.conf && \\\n    a2enmod ssl && \\\n    rm /etc/apache2/sites-enabled/000-default.conf && \\\n    chmod a+rx /docker-entrypoint.sh  # NOSONAR\n\nWORKDIR /var/www/acme2certifier/\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"/usr/sbin/apache2ctl\", \"-D\", \"FOREGROUND\"]\n\nEXPOSE 80 443\n"
  },
  {
    "path": "examples/Docker/apache2/wsgi/docker-entrypoint.sh",
    "content": "#!/bin/bash\n\n# create acme-srv.cfg if not existing\nif [[ ! -f /var/www/acme2certifier/volume/acme_srv.cfg ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/acme_srv.cfg /var/www/acme2certifier/volume/\nfi\n\n# enable tls if acme2certifier.pm exists on volume\nif [[ -f /var/www/acme2certifier/volume/acme2certifier.pem ]]\nthen\n    echo \"found acme2certifier.pem! enable TLS\" >> /proc/1/fd/1\n   cp  /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-enabled/acme2certifier_ssl.conf\nfi\n\n# create ca_handler if:\n# - ca_handler.py does not exists in volume AND\n# - no entry handler_file: exists in acme_srv.cfg\n# - define ca_handler defined under handler_file does not exists\nif ( [[ ! -f /var/www/acme2certifier/volume/ca_handler.py ]] && \\\n     ! ( grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg &> /dev/null && \\\n         [[ -f $(grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg | awk -F\":\" '{print $2}') ]] \\\n        ))\nthen\n    echo \"no ca_handler.py found! creating from skeleton_ca_handler.py\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/ca_handler/skeleton_ca_handler.py /var/www/acme2certifier/volume/ca_handler.py\nelse\n    if [[ -f /var/www/acme2certifier/volume/ca_handler.py ]]\n    then\n        sed -i \"s/from acme.helper import/from acme_srv.helper import/g\" /var/www/acme2certifier/volume/ca_handler.py\n    fi\nfi\n\n# create symlink for the acme_srv.cfg\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    chown www-data.www-data /var/www/acme2certifier/volume/acme_srv.cfg\nfi\n\n# create symlink for the acme_srv.db\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.db ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.db /var/www/acme2certifier/acme_srv/acme_srv.db\nfi\n\n# apply database updates (if needed)\npython3 /var/www/acme2certifier/tools/db_update.py\n\n# create symlink for the ca_handler\nif [[ ! -L /var/www/acme2certifier/acme_srv/ca_handler.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/ca_handler.py /var/www/acme2certifier/acme_srv/ca_handler.py\nfi\n\nchown -R www-data /var/www/acme2certifier/volume\nchmod u+s /var/www/acme2certifier/volume/\nexec \"$@\"\n"
  },
  {
    "path": "examples/Docker/docker-compose.yml",
    "content": "services:\n  acme-srv:\n    build:\n      context: ../../.\n      dockerfile: examples/Docker/$WEBSERVER/$CONTEXT/Dockerfile\n    image: acme2certifier/$CONTEXT\n    volumes:\n      - type: bind\n        source: ./data\n        target: /var/www/acme2certifier/volume/\n        read_only: false\n    ports:\n      - \"22280:80\"\n      - \"22443:443\"\n    restart: always\n\nnetworks:\n  default:\n    name: acme\n    external: true\n"
  },
  {
    "path": "examples/Docker/nginx/django/Dockerfile",
    "content": "FROM ubuntu:24.04\nLABEL maintainer=\"grindelsack@gmail.com\"\n\nRUN apt-get update  && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install -y --no-install-recommends tzdata && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n    curl \\\n    krb5-user \\\n    libgssapi-krb5-2 \\\n    libkrb5-3 \\\n    nginx \\\n    python3-django \\\n    python3-gssapi \\\n    python3-mysqldb \\\n    python3-pip \\\n    python3-psycopg2 \\\n    python3-pymysql \\\n    python3-yaml \\\n    uwsgi \\\n    uwsgi-plugin-python3 && \\\n    curl https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb --output /tmp/packages-microsoft-prod.deb && \\\n    dpkg -i /tmp/packages-microsoft-prod.deb && \\\n    rm /tmp/packages-microsoft-prod.deb && \\\n    apt-get update && \\\n    ACCEPT_EULA=Y apt-get install -y msodbcsql18 && \\\n    apt-get clean &&  \\\n    rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* && \\\n    mkdir -p /var/www/acme2certifier/volume && \\\n    mkdir -p /var/www/acme2certifier/examples /var/www/acme2certifier/examples/ && \\\n    mkdir -p /run/uwsgi\n\nCOPY ./ /var/www/acme2certifier/\n\nRUN pip3 install impacket --break-system-packages && \\\n    rm -rf /usr/local/bin/* && \\\n    rm -rf /usr/local/lib/python3.12/dist-packages/impacket/examples/* && \\\n    pip3 install -r /var/www/acme2certifier/requirements.txt --break-system-packages && \\\n    pip3 install supervisor --break-system-packages && \\\n    pip3 install --no-cache-dir mssql-django pyodbc --break-system-packages --break-system-packages && \\\n    cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/  && \\\n    cp /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py  && \\\n\tcp /var/www/acme2certifier/examples/nginx/acme2certifier.ini /var/www/acme2certifier && \\\n\tcp /var/www/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf && \\\n\tcp /var/www/acme2certifier/examples/nginx/supervisord.conf /etc && \\\n\tln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf && \\\n\tchown -R www-data:www-data /var/www/acme2certifier && \\\n    cp /var/www/acme2certifier/examples/Docker/nginx/django/docker-entrypoint.sh /docker-entrypoint.sh && \\\n    sed -i \"s/acme2certifier_wsgi/acme2certifier.wsgi/g\" /var/www/acme2certifier/acme2certifier.ini && \\\n    sed -i \"s/nginx/www-data/g\" /var/www/acme2certifier/acme2certifier.ini && \\\n    ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && \\\n    sed -i \"s/default = default_sect/\\default = default_sect\\nlegacy = legacy_sect/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\[default_sect\\]/\\[default_sect\\]\\nactivate = 1\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf && \\\n    rm /etc/nginx/sites-enabled/default && \\\n    rm /var/www/acme2certifier/setup.py && \\\n    rm /var/www/acme2certifier/requirements.txt && \\\n\trm -rf /var/www/acme2certifier/examples/Docker && \\\n    rm -rf /var/www/acme2certifier/examples/db_handler && \\\n    rm -rf /var/www/acme2certifier/examples/apache2 && \\\n    rm -rf /var/www/acme2certifier/examples/acme_srv.db.example && \\\n    rm -rf /var/www/acme2certifier/examples/acme2certifier_wsgi.py  && \\\n\trm /var/www/acme2certifier/acme2certifier/settings.py && \\\n\tchmod a+rx /docker-entrypoint.sh  # NOSONAR\n\nWORKDIR /var/www/acme2certifier/\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"/usr/local/bin/supervisord\"]\n\nEXPOSE 80 443\n"
  },
  {
    "path": "examples/Docker/nginx/django/docker-entrypoint.sh",
    "content": "#!/bin/bash\n\n# create acme-srv.cfg if not existing\nif [[ ! -f /var/www/acme2certifier/volume/acme_srv.cfg ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/acme_srv.cfg /var/www/acme2certifier/volume/\nfi\n\n# enable ssl if acme2certifier_cert.pem and acme2certifier_key.pem exist on volume\nif  [[ -f /var/www/acme2certifier/volume/acme2certifier_cert.pem ]] && \\\n    [[ -f /var/www/acme2certifier/volume/acme2certifier_key.pem ]] && \\\n    [[ ! -f /etc/nginx/sites-available/acme_srv_ssl.conf ]]\nthen\n    cp  /var/www/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n    ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\nfi\n\n# create ca_handler if:\n# - ca_handler.py does not exists in volume AND\n# - no entry handler_file: exists in acme_srv.cfg\n# - define ca_handler defined under handler_file does not exists\nif ( [[ ! -f /var/www/acme2certifier/volume/ca_handler.py ]] && \\\n     ! ( grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg &> /dev/null && \\\n         [[ -f $(grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg | awk -F\":\" '{print $2}') ]] \\\n        ))\nthen\n    echo \"no ca_handler.py found! creating from skeleton_ca_handler.py\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/ca_handler/skeleton_ca_handler.py /var/www/acme2certifier/volume/ca_handler.py\nelse\n    if [[ -f /var/www/acme2certifier/volume/ca_handler.py ]]\n    then\n        sed -i \"s/from acme.helper import/from acme_srv.helper import/g\" /var/www/acme2certifier/volume/ca_handler.py\n    fi\nfi\n\n# create symlink for the acme_srv.cfg\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    chown www-data /var/www/acme2certifier/volume/acme_srv.cfg\nfi\n\n# create symlink for the acme_srv.db\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.db ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.db /var/www/acme2certifier/acme_srv/acme_srv.db\nfi\n\n# create symlink for the ca_handler\nif [[ ! -L /var/www/acme2certifier/acme_srv/ca_handler.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/ca_handler.py /var/www/acme2certifier/acme_srv/ca_handler.py\nfi\n\n# create settings.py if not existing\nif [[ ! -f /var/www/acme2certifier/volume/settings.py ]]\nthen\n    echo \"no settings.py found! copy settings.py\"\n    egrep -v '(# SECURITY WARNING: keep the secret key used in production secret!|^SECRET_KEY)' /var/www/acme2certifier/examples/django/acme2certifier/settings.py > /var/www/acme2certifier/volume/settings.py\n    ## generate SECRET_KEY\n    echo \"generating SECRET_KEY\"\n    DJANGO_SECRET_KEY=$(python3 tools/django_secret_keygen.py)\n    cat >>/var/www/acme2certifier/volume/settings.py <<EOF\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '${DJANGO_SECRET_KEY}'\nEOF\n    echo \"adding '*' wildcard hosts in settings.py\" >> /proc/1/fd/1\n    sed -i \"s/ALLOWED_HOSTS = \\['127.0.0.1'\\]/ALLOWED_HOSTS = \\['127.0.0.1','*'\\]/g\" /var/www/acme2certifier/volume/settings.py\nfi\n\n# create migrations if not existing\nif [[ ! -d /var/www/acme2certifier/volume/migrations ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp  -R /var/www/acme2certifier/examples/django/acme_srv/migrations /var/www/acme2certifier/volume/\n    # mkdir -p /var/www/acme2certifier/volume/migrations\nfi\n\n# create a symlink for migrations\nif [[ ! -L /var/www/acme2certifier/acme_srv/migrations ]]\nthen\n    if [[ -d /var/www/acme2certifier/volume/migrations ]]\n    then\n        echo \"delete migration directory\" >> /proc/1/fd/1\n        rm -rf /var/www/acme2certifier/acme_srv/migrations\n    fi\n    echo \"create symlink for migration directory\" >> /proc/1/fd/1\n    ln -s /var/www/acme2certifier/volume/migrations /var/www/acme2certifier/acme_srv/\nfi\n\n# create a symlink for settings.py\nif [[ ! -L /var/www/acme2certifier/acme2certifier/settings.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/settings.py /var/www/acme2certifier/acme2certifier/settings.py\nfi\n\n# check if we need to remove django_rename app\nif ( grep \"    'django_rename_app',\" /var/www/acme2certifier/volume/settings.py &> /dev/null)\nthen\n    echo \"remove django_rename application\" >> /proc/1/fd/1\n    sed -i \"/    'django_rename_app',/d\" /var/www/acme2certifier/volume/settings.py\nfi\n\necho \"apply migrations\"  >> /proc/1/fd/1\ntouch /var/www/acme2certifier/acme_srv/migrations/__init__.py\npython3 /var/www/acme2certifier/tools/django_update.py\npython3 manage.py loaddata acme_srv/fixture/status.yaml\n\nchown -R www-data /var/www/acme2certifier/volume\nchmod u+s /var/www/acme2certifier/volume/\n\nexec \"$@\"\n"
  },
  {
    "path": "examples/Docker/nginx/wsgi/Dockerfile",
    "content": "FROM ubuntu:24.04\nLABEL maintainer=\"grindelsack@gmail.com\"\nRUN apt-get update  && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install -y --no-install-recommends tzdata && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n    curl \\\n    krb5-user \\\n    libgssapi-krb5-2 \\\n    libkrb5-3 \\\n    nginx \\\n    python3-gssapi \\\n    python3-pip \\\n    uwsgi \\\n    uwsgi-plugin-python3 && \\\n    rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* && \\\n    mkdir -p /var/www/acme2certifier/volume && \\\n    mkdir -p /var/www/acme2certifier/examples /var/www/acme2certifier/examples/ && \\\n    mkdir -p /run/uwsgi\n\nCOPY ./ /var/www/acme2certifier/\n\n# configure acme2certifier\nRUN pip3 install impacket --break-system-packages && \\\n    rm -rf /usr/local/bin/* && \\\n    rm -rf /usr/local/lib/python3.12/dist-packages/impacket/examples/* && \\\n    pip3 install -r /var/www/acme2certifier/requirements.txt --break-system-packages && \\\n    pip3 install supervisor --break-system-packages && \\\n    cp /var/www/acme2certifier/examples/acme2certifier_wsgi.py /var/www/acme2certifier/acme2certifier_wsgi.py && \\\n    cp /var/www/acme2certifier/examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py && \\\n\tcp /var/www/acme2certifier/examples/nginx/acme2certifier.ini /var/www/acme2certifier && \\\n\tcp /var/www/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf && \\\n\tcp /var/www/acme2certifier/examples/nginx/supervisord.conf /etc && \\\n\tchown -R www-data /var/www/acme2certifier && \\\n\tln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf && \\\n    cp /var/www/acme2certifier/examples/Docker/nginx/wsgi/docker-entrypoint.sh /docker-entrypoint.sh && \\\n    # echo \"plugins=python3\" >> /var/www/acme2certifier/acme2certifier.ini && \\\n    sed -i \"s/nginx/www-data/g\" /var/www/acme2certifier/acme2certifier.ini && \\\n    ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && \\\n    sed -i \"s/default = default_sect/\\default = default_sect\\nlegacy = legacy_sect/g\" /etc/ssl/openssl.cnf && \\\n    sed -i \"s/\\[default_sect\\]/\\[default_sect\\]\\nactivate = 1\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf && \\\n    rm /etc/nginx/sites-enabled/default && \\\n    rm /var/www/acme2certifier/setup.py && \\\n    rm /var/www/acme2certifier/requirements.txt && \\\n\trm -rf /var/www/acme2certifier/examples/Docker && \\\n    rm -rf /var/www/acme2certifier/examples/django && \\\n    rm -rf /var/www/acme2certifier/examples/db_handler && \\\n    rm -rf /var/www/acme2certifier/examples/apache2 && \\\n    rm -rf /var/www/acme2certifier/examples/acme_srv.db.example && \\\n    rm -rf /var/www/acme2certifier/examples/acme2certifier_wsgi.py  && \\\n\tchmod a+rx /docker-entrypoint.sh  # NOSONAR\n\nWORKDIR /var/www/acme2certifier\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"/usr/local/bin/supervisord\"]\n\n# CMD [\"/bin/bash\"]\n\nEXPOSE 80 443\n"
  },
  {
    "path": "examples/Docker/nginx/wsgi/docker-entrypoint.sh",
    "content": "#!/bin/bash\n\n# create acme-srv.cfg if not existing\nif [[ ! -f /var/www/acme2certifier/volume/acme_srv.cfg ]]\nthen\n    echo \"no acme_srv.cfg found! creating acme_srv.cfg\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/acme_srv.cfg /var/www/acme2certifier/volume/\nfi\n\n# enable ssl if acme2certifier_cert.pem and acme2certifier_key.pem exist on volume\nif  [[ -f /var/www/acme2certifier/volume/acme2certifier_cert.pem ]] && \\\n    [[ -f /var/www/acme2certifier/volume/acme2certifier_key.pem ]] && \\\n    [[ ! -f /etc/nginx/sites-available/acme_srv_ssl.conf ]]\nthen\n    cp  /var/www/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n    ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\nfi\n\n# create ca_handler if:\n# - ca_handler.py does not exists in volume AND\n# - no entry handler_file: exists in acme_srv.cfg\n# - define ca_handler defined under handler_file does not exists\nif ( [[ ! -f /var/www/acme2certifier/volume/ca_handler.py ]] && \\\n     ! ( grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg &> /dev/null && \\\n         [[ -f $(grep -E '^handler_file:' /var/www/acme2certifier/volume/acme_srv.cfg | awk -F\":\" '{print $2}') ]] \\\n        ))\nthen\n    echo \"no ca_handler.py found! creating from skeleton_ca_handler.py\" >> /proc/1/fd/1\n    cp /var/www/acme2certifier/examples/ca_handler/skeleton_ca_handler.py /var/www/acme2certifier/volume/ca_handler.py\nelse\n    if [[ -f /var/www/acme2certifier/volume/ca_handler.py ]]\n    then\n        sed -i \"s/from acme.helper import/from acme_srv.helper import/g\" /var/www/acme2certifier/volume/ca_handler.py\n    fi\nfi\n\n# create symlink for the acme_srv.cfg\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    chown www-data.www-data /var/www/acme2certifier/volume/acme_srv.cfg\nfi\n\n# create symlink for the acme_srv.db\nif [[ ! -L /var/www/acme2certifier/acme_srv/acme_srv.db ]]\nthen\n    ln -s /var/www/acme2certifier/volume/acme_srv.db /var/www/acme2certifier/acme_srv/acme_srv.db\nfi\n\n# apply database updates (if needed)\npython3 /var/www/acme2certifier/tools/db_update.py\n\n# create symlink for the ca_handler\nif [[ ! -L /var/www/acme2certifier/acme_srv/ca_handler.py ]]\nthen\n    ln -s /var/www/acme2certifier/volume/ca_handler.py /var/www/acme2certifier/acme_srv/ca_handler.py\nfi\n\nchown -R www-data /var/www/acme2certifier/volume\nchmod u+s /var/www/acme2certifier/volume/\nexec \"$@\"\n"
  },
  {
    "path": "examples/Docker/soap-srv/Dockerfile",
    "content": "FROM ubuntu:22.04\nLABEL maintainer=\"grindelsack@gmail.com\"\n\nRUN apt-get update  && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get -y install --no-install-recommends tzdata && \\\n    DEBIAN_FRONTEND=\"noninteractive\" apt-get install --no-install-recommends -y \\\n    apache2 \\\n    apache2-data \\\n    curl \\\n    krb5-user \\\n    libapache2-mod-wsgi-py3 \\\n    libgssapi-krb5-2 \\\n    libkrb5-3 \\\n    python3-gssapi \\\n    python3-pip \\\n    && rm -rf /var/lib/apt/lists/*\n\n\n# install python requirements\nCOPY requirements.txt /tmp/requirements.txt\nRUN pip3 install -r /tmp/requirements.txt &&\\\n    mkdir -p /usr/local/soap-srv/acme_srv && \\\n\tmkdir -p /usr/local/soap-srv/examples/ca_handler\n\nCOPY examples/soap/mock_soap_srv.py /usr/local/soap-srv/\nCOPY acme_srv/helper.py acme_srv/version.py /usr/local/soap-srv/acme_srv/\nCOPY acme_srv/helpers /usr/local/soap-srv/acme_srv/helpers\nCOPY examples/ca_handler/xca_ca_handler.py /usr/local/soap-srv/examples/ca_handler/xca_ca_handler.py\nCOPY examples/Docker/soap-srv/docker-entrypoint.sh /docker-entrypoint.sh\nRUN chmod a+rx /docker-entrypoint.sh\n\nWORKDIR /tmp\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"python3\", \"/usr/local/soap-srv/mock_soap_srv.py\", \"-d\", \"-c\", \"/etc/soap-srv/soap_srv.cfg\"]\n# CMD [\"tail\" , \"-f\", \"/dev/null\"]\nEXPOSE 80\n"
  },
  {
    "path": "examples/Docker/soap-srv/docker-entrypoint.sh",
    "content": "#!/bin/bash\n\n# chown -R www-data /etc/www/acme2certifier/volume\nchmod u+s /usr/local/soap-srv/mock_soap_srv.py\nexec \"$@\"\n"
  },
  {
    "path": "examples/Docker/soap_srv.yml",
    "content": "services:\n  soap-srv:\n    build:\n      context: ../../.\n      dockerfile: examples/Docker/soap-srv/Dockerfile\n    image: soap-srv\n    volumes:\n      - type: bind\n        source: ./data\n        target: /etc/soap-srv\n        read_only: false\n    ports:\n      - \"8888:8888\"\n    restart: always\n\nnetworks:\n  default:\n    external: true\n    name: acme\n"
  },
  {
    "path": "examples/Docker/ubuntu-systemd/deb_tester.sh",
    "content": "#!/bin/bash\necho \"${1}\"\necho \"${2}\"\n\ncase \"${1}\" in\n\n  \"restart\")\n    echo \"update configuration and restart service\"\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv\n    yes | cp -R /tmp/acme2certifier/volume/acme_ca/* /var/www/acme2certifier/volume/acme_ca/\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      systemctl restart apache2\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      systemctl restart nginx\n      systemctl restart acme2certifier\n    fi\n    ;;\n\n  *)\n    echo \"install missing packages\"\n    apt-get update\n    apt-get -y upgrade\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3 rsyslog\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 rsyslog\n    fi\n\n    apt-get install -y python3-pip\n    pip install requests-pkcs12 --break-system-packages\n   # pip install pyopenssl --upgrade\n\n    systemctl enable rsyslog\n    systemctl start syslog\n\n    echo \"install a2c\"\n    apt-get install -y /tmp/acme2certifier/acme2certifier*.deb\n\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      echo \"configure apache\"\n      cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\n      cp /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n      a2enmod ssl\n      a2ensite acme2certifier\n      a2ensite acme2certifier_ssl\n      rm /etc/apache2/sites-enabled/000-default.conf\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      echo \"configure nginx\"\n      cp /var/www/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\n      cp /var/www/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n      rm /etc/nginx/sites-enabled/default\n      ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n      ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n      cp /var/www/acme2certifier/examples/nginx/acme2certifier.ini /var/www/acme2certifier\n      cp /var/www/acme2certifier/examples/nginx/acme2certifier.service /etc/systemd/system/acme2certifier.service\n      systemctl start acme2certifier\n      systemctl enable acme2certifier\n    fi\n\n    echo \"update openssl configuration\"\n    sed -i \"s/default = default_sect/default = default_sect\\nlegacy = legacy_sect\\n\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf\n    sed -i \"s/# activate = 1/activate = 1/g\" /etc/ssl/openssl.cnf\n\n    echo \"copy data\"\n    mkdir -p /var/www/acme2certifier/volume/\n    cp -R /tmp/acme2certifier/volume/* /var/www/acme2certifier/volume/\n\n    if [[ -f /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]; then\n      rm /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    fi\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n\n    echo \"change owner and start service\"\n    chown -R www-data.www-data /var/www/acme2certifier/\n    systemctl start \"${2}\"\n    ;;\nesac\n"
  },
  {
    "path": "examples/Docker/ubuntu-systemd/django_tester.sh",
    "content": "#!/bin/bash\necho \"${1}\"\necho \"${2}\"\n\ncase \"${1}\" in\n\n  \"restart\")\n    echo \"update configuration and restart service\"\n    yes | cp /tmp/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv\n    yes | cp -R /tmp/acme2certifier/volume/acme_ca/* /var/www/acme2certifier/volume/acme_ca/\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      systemctl restart apache2\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      systemctl restart nginx\n      systemctl restart acme2certifier\n    fi\n    ;;\n\n  *)\n    echo \"install missing packages\"\n    apt-get update\n    apt-get -y upgrade\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      apt-get install -y apache2  apache2-data  libapache2-mod-wsgi-py3 rsyslog\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 rsyslog\n    fi\n\n    apt-get install -y python3-pip\n    pip install requests-pkcs12 --break-system-packages\n    # pip install pyopenssl --upgrade\n\n    systemctl enable rsyslog\n    systemctl start syslog\n\n    if [[ -f /tmp/acme2certifier/packages-microsoft-prod.deb ]]\n      then\n      echo \"install Microsoft repository configuration package\"\n      dpkg -i /tmp/acme2certifier/packages-microsoft-prod.deb\n      apt-get update\n      ACCEPT_EULA=Y apt-get install -y msodbcsql18 python3-mssql-django\n    fi\n\n    echo \"install a2c\"\n    apt-get install -y /tmp/acme2certifier/acme2certifier*.deb\n\n    if [[ \"${2}\" = \"apache2\" ]]; then\n      echo \"configure apache\"\n      cp /var/www/acme2certifier/examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\n      cp /var/www/acme2certifier/examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\n      a2enmod ssl\n      a2ensite acme2certifier\n      a2ensite acme2certifier_ssl\n      rm /etc/apache2/sites-enabled/000-default.conf\n    elif [[ \"${2}\" = \"nginx\" ]]; then\n      echo \"configure nginx\"\n      cp /var/www/acme2certifier/examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\n      cp /var/www/acme2certifier/examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\n      rm /etc/nginx/sites-enabled/default\n      ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\n      ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n      cp /var/www/acme2certifier/examples/nginx/acme2certifier.ini /var/www/acme2certifier\n      cp /var/www/acme2certifier/examples/nginx/acme2certifier.service /etc/systemd/system/acme2certifier.service\n      systemctl start acme2certifier\n      systemctl enable acme2certifier\n    fi\n\n    echo \"update openssl configuration\"\n    sed -i \"s/default = default_sect/default = default_sect\\nlegacy = legacy_sect\\n\\n\\[legacy_sect\\]\\nactivate = 1/g\" /etc/ssl/openssl.cnf\n    sed -i \"s/# activate = 1/activate = 1/g\" /etc/ssl/openssl.cnf\n\n    echo \"configure django\"\n    cp -R /var/www/acme2certifier/examples/django/* /var/www/acme2certifier/\n    cp -r /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n\n    echo \"copy data\"\n    mkdir -p /var/www/acme2certifier/volume/\n    cp -R /tmp/acme2certifier/volume/* /var/www/acme2certifier/volume/\n\n    if [[ -f /var/www/acme2certifier/acme_srv/acme_srv.cfg ]]; then\n      rm /var/www/acme2certifier/acme_srv/acme_srv.cfg\n    fi\n    ln -s /var/www/acme2certifier/volume/acme_srv.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\n\n    if [[ -f /var/www/acme2certifier/acme2certifier/settings.py ]]; then\n      rm /var/www/acme2certifier/acme2certifier/settings.py\n    fi\n    ln -s /var/www/acme2certifier/volume/acme2certifier/settings.py /var/www/acme2certifier/acme2certifier/settings.py\n\n    echo \"appply migrations\"\n    cd /var/www/acme2certifier\n    python3 tools/django_update.py\n\n    echo \"change owner and start service\"\n    chown -R www-data.www-data /var/www/acme2certifier/volume\n    chown -R www-data.www-data /var/www/acme2certifier/\n\n    systemctl start \"${2}\"\n    ;;\nesac\n"
  },
  {
    "path": "examples/Docker/vault/compose.yaml",
    "content": "version: '3.3'\nservices:\n  vault:\n    image: hashicorp/vault\n    container_name: vault\n    environment:\n      VAULT_ADDR: \"https://vault.acme:8200\"\n      VAULT_API_ADDR: \"https://vault.acme:8200\"\n      VAULT_ADDRESS: \"https://vault.acme:8200\"\n      VAULT_SKIP_VERIFY: \"true\"\n      # VAULT_UI: true\n      # VAULT_TOKEN:\n    ports:\n      - \"8200:8200\"\n      - \"8201:8201\"\n    restart: always\n    volumes:\n      - ./data/logs:/vault/logs/:rw\n      - ./data/data:/vault/data/:rw\n      - ./config:/vault/config/:rw\n      - ./certs:/certs/:rw\n      - ./data/file:/vault/file/:rw\n    cap_add:\n      - IPC_LOCK\n    entrypoint: vault server -config /vault/config/config.hcl\nnetworks:\n  default:\n    external:\n      name: acme\n"
  },
  {
    "path": "examples/Docker/vault/config.hcl",
    "content": "ui = true\ndisable_mlock = \"true\"\n\nstorage \"raft\" {\n  path    = \"/vault/data\"\n  node_id = \"node1\"\n}\n\nlistener \"tcp\" {\n  address = \"[::]:8200\"\n  tls_disable = \"false\"\n  tls_cert_file = \"/certs/server.crt\"\n  tls_key_file  = \"/certs/server.key\"\n}\n\napi_addr = \"https://vault.acme:8200\"\ncluster_addr = \"https://vault.acme:8201\"\n"
  },
  {
    "path": "examples/acme2certifier_wsgi.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# pylint: disable=E0401, R1705\n\"\"\"wsgi based acme server\"\"\"\nfrom __future__ import print_function\nimport re\nimport json\nimport sys\nfrom wsgiref.simple_server import make_server, WSGIRequestHandler\nfrom acme_srv.account import Account\nfrom acme_srv.acmechallenge import Acmechallenge\nfrom acme_srv.authorization import Authorization\nfrom acme_srv.certificate import Certificate\nfrom acme_srv.challenge import Challenge\nfrom acme_srv.directory import Directory\nfrom acme_srv.housekeeping import Housekeeping\nfrom acme_srv.nonce import Nonce\nfrom acme_srv.order import Order\nfrom acme_srv.renewalinfo import Renewalinfo\nfrom acme_srv.trigger import Trigger\nfrom acme_srv.helper import (\n    get_url,\n    load_config,\n    logger_setup,\n    logger_info,\n    config_check,\n)\nfrom acme_srv.version import __dbversion__, __version__\n\n\n# We address a cpdesmells\nHTTP_CODE_DIC = {\n    200: \"Created\",\n    201: \"OK\",\n    400: \"Bad Request\",\n    401: \"Unauthorized\",\n    403: \"Forbidden\",\n    404: \"Not Found\",\n    405: \"Method Not Allowed\",\n    500: \"serverInternal \",\n}\n\nWRT_ERROR_MSG = json.dumps(\n    {\n        \"status\": 405,\n        \"message\": HTTP_CODE_DIC[405],\n        \"detail\": \"Wrong request type. Expected POST.\",\n    }\n).encode(\"utf-8\")\nCONTENT_TYPE_JSON = \"application/json\"\nWSGI_INPUT = \"wsgi.input\"\n\n# load config to set debug mode\nCONFIG = load_config()\ntry:\n    DEBUG = CONFIG.getboolean(\"DEFAULT\", \"debug\")\nexcept Exception:\n    DEBUG = False\n\nURL_PREFIX = CONFIG.get(\"Directory\", \"url_prefix\", fallback=None)\n\n\ndef err_wrong_request_method(start_response):\n    \"\"\"this is the error response for a wrong request method\"\"\"\n    start_response(f\"405 {HTTP_CODE_DIC[405]}\", [(\"Content-Type\", CONTENT_TYPE_JSON)])\n\n\ndef handle_exception(exc_type, exc_value, exc_traceback):\n    \"\"\"exception handler\"\"\"\n    if issubclass(exc_type, KeyboardInterrupt):\n        sys.__excepthook__(exc_type, exc_value, exc_traceback)  # pragma: no cover\n        return  # pragma: no cover\n\n    LOGGER.exception(\n        \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n    )\n\n\n# initialize logger\nLOGGER = logger_setup(DEBUG)\n\nwith Housekeeping(DEBUG, LOGGER) as db_check:\n    db_check.dbversion_check(__dbversion__)\n\n# examption handling via logger\nsys.excepthook = handle_exception\n\n\ndef create_header(response_dic, add_json_header=True):\n    \"\"\"create header\"\"\"\n    # generate header and nonce\n    if add_json_header:\n        if \"code\" in response_dic:\n            if response_dic[\"code\"] in (200, 201):\n                headers = [(\"Content-Type\", CONTENT_TYPE_JSON)]\n            else:\n                headers = [(\"Content-Type\", \"application/problem+json\")]\n        else:\n            headers = [(\"Content-Type\", CONTENT_TYPE_JSON)]\n    else:\n        headers = []\n\n    # enrich header\n    if \"header\" in response_dic:\n        for element, value in response_dic[\"header\"].items():\n            headers.append((element, value))\n\n    return headers\n\n\ndef get_request_body(environ):\n    \"\"\"get body from request data\"\"\"\n    try:\n        request_body_size = int(environ.get(\"CONTENT_LENGTH\", 0))\n    except ValueError:\n        request_body_size = 0\n    if WSGI_INPUT in environ:\n        request_body = environ[WSGI_INPUT].read(request_body_size)\n    else:\n        request_body = None\n    return request_body\n\n\ndef acct(environ, start_response):\n    \"\"\"account handling\"\"\"\n    with Account(DEBUG, get_url(environ), LOGGER) as account:\n        request_body = get_request_body(environ)\n        response_dic = account.parse(request_body)\n\n        # create header\n        headers = create_header(response_dic)\n        start_response(\n            f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n        )\n        return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n\ndef acmechallenge_serve(environ, start_response):\n    \"\"\"directory listing\"\"\"\n    with Acmechallenge(DEBUG, get_url(environ), LOGGER) as acmechallenge:\n        key_authorization = acmechallenge.lookup(environ[\"PATH_INFO\"])\n        if not key_authorization:\n            key_authorization = \"NOT FOUND\"\n            start_response(f\"404 {HTTP_CODE_DIC[404]}\", [(\"Content-Type\", \"text/html\")])\n        else:\n            start_response(f\"200 {HTTP_CODE_DIC[200]}\", [(\"Content-Type\", \"text/html\")])\n        # logging\n        logger_info(LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], {})\n        return [key_authorization.encode(\"utf-8\")]\n\n\ndef authz(environ, start_response):\n    \"\"\"authorization handling\"\"\"\n    if \"REQUEST_METHOD\" in environ and environ[\"REQUEST_METHOD\"] in (\"POST\", \"GET\"):\n        with Authorization(DEBUG, get_url(environ), LOGGER) as authorization:\n            if environ[\"REQUEST_METHOD\"] == \"POST\":\n                try:\n                    request_body_size = int(environ.get(\"CONTENT_LENGTH\", 0))\n                except ValueError:\n                    request_body_size = 0\n                request_body = environ[WSGI_INPUT].read(request_body_size)\n                response_dic = authorization.new_post(request_body)\n            else:\n                response_dic = authorization.new_get(get_url(environ, True))\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef newaccount(environ, start_response):\n    \"\"\"create new account\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n\n        with Account(DEBUG, get_url(environ), LOGGER) as account:\n            request_body = get_request_body(environ)\n            response_dic = account.new(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef directory(environ, start_response):\n    \"\"\"directory listing\"\"\"\n    with Directory(DEBUG, get_url(environ), LOGGER) as direct_tory:\n\n        response_dic = direct_tory.directory_get()\n        if \"error\" in response_dic:\n            headers = create_header({\"code\": 403})\n            start_response(f\"403 {HTTP_CODE_DIC[403]}\", headers)\n            return [\n                json.dumps(\n                    {\n                        \"status\": 403,\n                        \"message\": HTTP_CODE_DIC[403],\n                        \"detail\": response_dic[\"error\"],\n                    }\n                ).encode(\"utf-8\")\n            ]\n        else:\n            headers = create_header({\"code\": 200})\n            start_response(\"200 OK\", headers)\n            # logging\n            logger_info(LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], \"\")\n            return [json.dumps(response_dic).encode(\"utf-8\")]\n\n\ndef cert(environ, start_response):\n    \"\"\"create new account\"\"\"\n    with Certificate(DEBUG, get_url(environ), LOGGER) as certificate:\n        if environ[\"REQUEST_METHOD\"] == \"POST\":\n            request_body = get_request_body(environ)\n            response_dic = certificate.new_post(request_body)\n            # create header\n            headers = create_header(response_dic, False)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [response_dic[\"data\"].encode(\"utf-8\")]\n\n        elif environ[\"REQUEST_METHOD\"] == \"GET\":\n\n            response_dic = certificate.new_get(get_url(environ, True))\n            # create header\n            headers = create_header(response_dic)\n            # create the response\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            # send response\n            return [response_dic[\"data\"]]\n\n        else:\n            err_wrong_request_method(start_response)\n            return [WRT_ERROR_MSG]\n\n\ndef chall(environ, start_response):\n    \"\"\"create new account\"\"\"\n    with Challenge(\n        debug=DEBUG,\n        srv_name=get_url(environ),\n        source=environ[\"REMOTE_ADDR\"],\n        logger=LOGGER,\n    ) as challenge:\n        if environ[\"REQUEST_METHOD\"] == \"POST\":\n\n            request_body = get_request_body(environ)\n            response_dic = challenge.parse(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n        elif environ[\"REQUEST_METHOD\"] == \"GET\":\n\n            response_dic = challenge.get(get_url(environ, True))\n\n            # generate header\n            headers = [(\"Content-Type\", CONTENT_TYPE_JSON)]\n            # create the response\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            # send response\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n        else:\n            err_wrong_request_method(start_response)\n            return [WRT_ERROR_MSG]\n\n\ndef newnonce(environ, start_response):\n    \"\"\"generate a new nonce\"\"\"\n    if environ[\"REQUEST_METHOD\"] in [\"HEAD\", \"GET\"]:\n        nonce = Nonce(DEBUG, LOGGER)\n        headers = [\n            (\"Content-Type\", \"text/plain\"),\n            (\"Replay-Nonce\", f\"{nonce.generate_and_add()}\"),\n        ]\n        status = \"200 OK\" if environ[\"REQUEST_METHOD\"] == \"HEAD\" else \"204 No content\"\n        start_response(status, headers)\n        return []\n    else:\n        err_wrong_request_method(start_response)\n        return [\n            json.dumps(\n                {\n                    \"status\": 405,\n                    \"message\": HTTP_CODE_DIC[405],\n                    \"detail\": \"Wrong request type. Expected HEAD or GET.\",\n                }\n            ).encode(\"utf-8\")\n        ]\n\n\ndef neworders(environ, start_response):\n    \"\"\"generate a new order\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n        with Order(DEBUG, get_url(environ), LOGGER) as norder:\n            request_body = get_request_body(environ)\n            response_dic = norder.new(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef order(environ, start_response):\n    \"\"\"order_handler\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n        with Order(DEBUG, get_url(environ), LOGGER) as eorder:\n            request_body = get_request_body(environ)\n            response_dic = eorder.parse(request_body, environ)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef renewalinfo(environ, start_response):\n    \"\"\"renewalinfo handler\"\"\"\n    with Renewalinfo(DEBUG, get_url(environ), LOGGER) as renewalinfo_:\n        if environ[\"REQUEST_METHOD\"] == \"POST\":\n            request_body = get_request_body(environ)\n            response_dic = renewalinfo_.update(request_body)\n            # create header\n            headers = create_header(response_dic, False)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            return []\n\n        elif environ[\"REQUEST_METHOD\"] == \"GET\":\n\n            response_dic = renewalinfo_.get(get_url(environ, True))\n            # create header\n            headers = create_header(response_dic)\n            # create the response\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n\n            # send response\n            if \"data\" in response_dic:\n                return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n            else:\n                return []\n\n        else:\n            err_wrong_request_method(start_response)\n            return [WRT_ERROR_MSG]\n\n\ndef revokecert(environ, start_response):\n    \"\"\"revocation_handler\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n        with Certificate(DEBUG, get_url(environ), LOGGER) as certificate:\n            request_body = get_request_body(environ)\n            response_dic = certificate.revoke(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n            if \"data\" in response_dic:\n                return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n            else:\n                return []\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef trigger(environ, start_response):\n    \"\"\"ca trigger handler\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n        with Trigger(DEBUG, get_url(environ), LOGGER) as trigger_:\n            request_body = get_request_body(environ)\n            response_dic = trigger_.parse(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(\n                LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], response_dic\n            )\n\n            if \"data\" in response_dic:\n                return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n            else:\n                return []\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef housekeeping(environ, start_response):\n    \"\"\"cli housekeeping handler\"\"\"\n    if environ[\"REQUEST_METHOD\"] == \"POST\":\n        with Housekeeping(DEBUG, LOGGER) as housekeeping_:\n            request_body = get_request_body(environ)\n            response_dic = housekeeping_.parse(request_body)\n\n            # create header\n            headers = create_header(response_dic)\n            start_response(\n                f'{response_dic[\"code\"]} {HTTP_CODE_DIC[response_dic[\"code\"]]}', headers\n            )\n\n            # logging\n            logger_info(LOGGER, environ[\"REMOTE_ADDR\"], environ[\"PATH_INFO\"], \"****\")\n\n            if \"data\" in response_dic:\n                return [json.dumps(response_dic[\"data\"]).encode(\"utf-8\")]\n            else:\n                return []\n    else:\n        err_wrong_request_method(start_response)\n        return [WRT_ERROR_MSG]\n\n\ndef not_found(_environ, start_response):\n    \"\"\"called if no URL matches\"\"\"\n    start_response(\"404 NOT FOUND\", [(\"Content-Type\", \"text/plain\")])\n    return [\n        json.dumps(\n            {\"status\": 404, \"message\": HTTP_CODE_DIC[404], \"detail\": \"Not Found\"}\n        ).encode(\"utf-8\")\n    ]\n\n\ndef redirect(environ, start_response):\n    \"\"\"redirect to directory ressource\"\"\"\n    if URL_PREFIX:\n        start_response(\"302 Found\", [(\"Location\", URL_PREFIX + \"/directory\")])\n    else:\n        # redirect to directory\n        start_response(\"302 Found\", [(\"Location\", \"/directory\")])\n    return []\n\n\n# map urls to functions\nURLS = [\n    (r\"^$\", redirect),\n    (r\"^acme/acct\", acct),\n    (r\"^acme/authz\", authz),\n    (r\"^acme/cert\", cert),\n    (r\"^acme/chall\", chall),\n    (r\"^acme/directory\", directory),\n    (r\"^acme/key-change\", acct),\n    (r\"^acme/newaccount$\", newaccount),\n    (r\"^acme/newnonce$\", newnonce),\n    (r\"^acme/neworders$\", neworders),\n    (r\"^acme/order\", order),\n    (r\"^acme/renewal-info\", renewalinfo),\n    (r\"^acme/revokecert\", revokecert),\n    (r\"^directory?$\", directory),\n    (r\"^housekeeping\", housekeeping),\n    (r\"^trigger\", trigger),\n]\n\n\ndef application(environ, start_response):\n    \"\"\"The main WSGI application if nothing matches call the not_found function.\"\"\"\n\n    # check if we need to activate the url pattern for challenge verification\n    if \"CAhandler\" in CONFIG and \"acme_url\" in CONFIG[\"CAhandler\"]:\n        URLS.append((r\"^.well-known/acme-challenge/\", acmechallenge_serve))\n\n    prefix = \"/\"\n    if \"Directory\" in CONFIG and \"url_prefix\" in CONFIG[\"Directory\"]:\n        prefix = CONFIG[\"Directory\"][\"url_prefix\"] + \"/\"\n    path = environ.get(\"PATH_INFO\", \"\").lstrip(prefix)\n\n    for regex, callback in URLS:\n        match = re.search(regex, path)\n        if match is not None:\n            environ[\"myapp.url_args\"] = match.groups()\n            return callback(environ, start_response)\n    return not_found(environ, start_response)\n\n\ndef get_handler_cls():\n    \"\"\"my handler to disable name resolution\"\"\"\n    cls = WSGIRequestHandler\n\n    # disable dns resolution in BaseHTTPServer.py\n    class Acme2certiferhandler(cls, object):\n        \"\"\"source: https://review.opendev.org/#/c/79876/9/ceilometer/api/app.py\"\"\"\n\n        def address_string(self):\n            return self.client_address[0]  # pragma: no cover\n\n    return Acme2certiferhandler\n\n\nif __name__ == \"__main__\":\n\n    LOGGER.info(\"Starting acme2certifier version %s\", __version__)  # pragma: no cover\n\n    # check configuration for parameters masked in \"\"\n    config_check(LOGGER, CONFIG)  # pragma: no cover\n\n    SRV = make_server(\n        \"0.0.0.0\", 80, application, handler_class=get_handler_cls()\n    )  # pragma: no cover\n    SRV.serve_forever()  # pragma: no cover\n"
  },
  {
    "path": "examples/acme_srv.cfg",
    "content": "[DEFAULT]\ndebug: False\n\n[Nonce]\n# disable nonce check. THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nnonce_check_disable: False\n\n[CAhandler]\n# CA specific options\n\n[DBhandler]\n#dbfile: /var/lib/acme/db.sqlite3\n\n[Certificate]\nrevocation_reason_check_disable: False\n\n[Challenge]\n# when true disable challenge validation. Challenge will be set to 'valid' without further checking\n# THIS IS A SEVERE SECURTIY ISSUE! Please do only for testing/debugging purposes\nchallenge_validation_disable: False\n\n[Order]\ntnauthlist_support: False\n"
  },
  {
    "path": "examples/apache2/apache_django.conf",
    "content": "<VirtualHost *:80>\n        DocumentRoot /var/www/acme2certifier/\n        WSGIDaemonProcess acme_srv\n        WSGIProcessGroup acme_srv\n        WSGIApplicationGroup %{GLOBAL}\n        WSGIScriptAlias / /var/www/acme2certifier/acme2certifier/wsgi.py\n        <Directory /var/www/acme2certifier>\n        <Files wsgi.py>\n        Require all granted\n        AcceptPathInfo On\n        </Files>\n        </Directory>\n</VirtualHost>\n"
  },
  {
    "path": "examples/apache2/apache_django_ssl.conf",
    "content": "<IfModule mod_ssl.c>\n<VirtualHost *:443>\n        DocumentRoot /var/www/acme2certifier/\n        WSGIDaemonProcess acme_srv_ssl\n        WSGIProcessGroup acme_srv_ssl\n        WSGIApplicationGroup %{GLOBAL}\n        WSGIScriptAlias / /var/www/acme2certifier/acme2certifier/wsgi.py\n        <Directory /var/www/acme2certifier>\n        <Files wsgi.py>\n        Require all granted\n        AcceptPathInfo On\n        </Files>\n        </Directory>\n        SSLEngine on\n        SSLCertificateFile /var/www/acme2certifier/volume/acme2certifier.pem\n</VirtualHost>\n</IfModule>\n"
  },
  {
    "path": "examples/apache2/apache_wsgi.conf",
    "content": "# a2enmod cgi\n# a2enmod rewrite\n<VirtualHost *:80>\n        DocumentRoot /var/www/acme2certifier/\n        WSGIDaemonProcess acme_srv python-path=/var/www/acme2certifier\n        WSGIProcessGroup acme_srv\n        WSGIApplicationGroup %{GLOBAL}\n        WSGIScriptAlias / /var/www/acme2certifier/acme2certifier_wsgi.py\n        <Directory /var/www/acme2certifier>\n        Order allow,deny\n        Allow from all\n        </Directory>\n</VirtualHost>\n"
  },
  {
    "path": "examples/apache2/apache_wsgi_ssl.conf",
    "content": "<IfModule mod_ssl.c>\n<VirtualHost *:443>\n        DocumentRoot /var/www/acme2certifier/\n        WSGIDaemonProcess acme_srv_ssl python-path=/var/www/acme2certifier\n        WSGIProcessGroup acme_srv_ssl\n        WSGIApplicationGroup %{GLOBAL}\n        WSGIScriptAlias / /var/www/acme2certifier/acme2certifier_wsgi.py\n        <Directory /var/www/acme2certifier>\n        Order allow,deny\n        Allow from all\n        </Directory>\n        SSLEngine on\n        SSLCertificateFile /var/www/acme2certifier/volume/acme2certifier.pem\n</VirtualHost>\n</IfModule>\n"
  },
  {
    "path": "examples/ca_handler/__init__.py",
    "content": ""
  },
  {
    "path": "examples/ca_handler/acme_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"generic ca handler for CAs supporting acme protocol\"\"\"\nfrom __future__ import print_function\n\n# pylint: disable= e0401, w0105, w0212\nimport json\nimport textwrap\nimport os.path\nfrom typing import Tuple, Dict\nimport requests\nimport josepy\nimport subprocess\nimport time\nimport shlex\nfrom threading import Thread\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom OpenSSL import crypto\nfrom acme import client, messages, errors\nfrom acme_srv.db_handler import DBstore\nfrom acme_srv.helper import (\n    allowed_domainlist_check,\n    b64_encode,\n    b64_url_encode,\n    b64_url_recode,\n    b64_url_decode,\n    cert_pem2der,\n    client_parameter_validate,\n    config_allowed_domainlist_load,\n    config_eab_profile_load,\n    config_headerinfo_load,\n    config_enroll_config_log_load,\n    config_profile_load,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    load_config,\n    parse_url,\n    url_get,\n    sha256_hash,\n    txt_get,\n    uts_now,\n    uts_to_date_utc,\n    handler_config_check,\n)\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.account = None\n        self.acme_keypath = None\n        self.acme_keyfile = None\n        self.acme_sh_script = None\n        self.acme_sh_shell = None\n        self.acme_url = None\n        self.acme_url_dic = {}\n        self.allowed_domainlist = []\n        self.dbstore = DBstore(None, self.logger)\n        self.dns_update_script = None\n        self.dns_update_script_variables = None\n        self.dns_validation_timeout = 20\n        self.dns_record_dic = {}\n        self.eab_handler = None\n        self.eab_kid = None\n        self.eab_hmac_key = None\n        self.eab_profiling = False\n        self.email = None\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.header_info_field = False\n        self.key_size = 2048\n        self.path_dic = {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"}\n        self.profile = None\n        self.profiles = {}\n        self.ssl_verify = True\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.acme_url:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"close the connection at the end of the context\"\"\"\n\n    def _config_account_load(self, config_dic: Dict[str, str]):\n        self.logger.debug(\"CAhandler._config_account_load()\")\n\n        self.acme_keyfile = config_dic.get(\"CAhandler\", \"acme_keyfile\", fallback=None)\n        self.acme_url = config_dic.get(\"CAhandler\", \"acme_url\", fallback=None)\n        self.acme_url_dic = parse_url(self.logger, self.acme_url)\n\n        for ele in (\"acme_keyfile\", \"acme_url\"):\n            if not getattr(self, ele):\n                self.logger.error(\n                    'acme_ca_handler configuration incomplete: \"%s\" parameter is missing in config file',\n                    ele,\n                )\n\n        self.path_dic[\"acct_path\"] = config_dic[\"CAhandler\"].get(\n            \"account_path\", \"/acme/acct/\"\n        )\n        self.key_size = config_dic.get(\n            \"CAhandler\", \"acme_account_keysize\", fallback=2048\n        )\n        self.account = config_dic.get(\"CAhandler\", \"acme_account\", fallback=None)\n        self.email = config_dic.get(\"CAhandler\", \"acme_account_email\", fallback=None)\n\n        if \"ssl_verify\" in config_dic[\"CAhandler\"]:\n            try:\n                self.ssl_verify = config_dic.getboolean(\n                    \"CAhandler\", \"ssl_verify\", fallback=False\n                )\n            except Exception as err:\n                self.logger.warning(\"Failed to parse ssl_verify parameter: %s\", err)\n        self.logger.debug(\"CAhandler._config_account_load() ended\")\n\n    def _config_parameters_load(self, config_dic: Dict[str, str]):\n        \"\"\" \" load eab config\"\"\"\n        self.logger.debug(\"CAhandler._config_eab_load()\")\n\n        self.path_dic[\"directory_path\"] = config_dic.get(\n            \"CAhandler\", \"directory_path\", fallback=self.path_dic[\"directory_path\"]\n        )\n        self.eab_kid = config_dic.get(\"CAhandler\", \"eab_kid\", fallback=None)\n        self.eab_hmac_key = config_dic.get(\"CAhandler\", \"eab_hmac_key\", fallback=None)\n        self.acme_keypath = config_dic.get(\"CAhandler\", \"acme_keypath\", fallback=None)\n        # load profile from config file if set\n        self.profile = config_dic.get(\"CAhandler\", \"profile\", fallback=None)\n\n        self.logger.debug(\"CAhandler._config_eab_load() ended\")\n\n    def _config_dns_update_script_load(self, config_dic: Dict[str, str]):\n        \"\"\" \" load dns update script\"\"\"\n        self.logger.debug(\"CAhandler._config_dns_update_script_load()\")\n\n        self.dns_update_script = config_dic.get(\n            \"CAhandler\", \"dns_update_script\", fallback=None\n        )\n        if self.dns_update_script and not os.path.exists(self.dns_update_script):\n            self.logger.error(\n                'CAhandler._config_dns_update_script_load(): dns update script \"%s\" does not exist',\n                self.dns_update_script,\n            )\n            self.dns_update_script = None\n\n        if self.dns_update_script:\n            self.logger.debug(\n                \"CAhandler._config_dns_update_script_load(): dns update script: %s\",\n                self.dns_update_script,\n            )\n\n            self.acme_sh_script = config_dic.get(\n                \"CAhandler\", \"acme_sh_script\", fallback=None\n            )\n            if self.acme_sh_script and not os.path.exists(self.acme_sh_script):\n                self.logger.error(\n                    'CAhandler._config_dns_update_script_load(): acme.sh script \"%s\" does not exist',\n                    self.acme_sh_script,\n                )\n                self.acme_sh_script = None\n\n            self.acme_sh_shell = config_dic.get(\n                \"CAhandler\", \"acme_sh_shell\", fallback=self.acme_sh_shell\n            )\n\n            try:\n                self.dns_validation_timeout = int(\n                    config_dic.get(\n                        \"CAhandler\",\n                        \"dns_validation_timeout\",\n                        fallback=self.dns_validation_timeout,\n                    )\n                )\n            except Exception as err:\n                self.logger.warning(\n                    \"CAhandler._config_dns_update_script_load(): Failed to parse dns_validation_timeout parameter: %s\",\n                    err,\n                )\n\n            try:\n                self.dns_update_script_variables = json.loads(\n                    config_dic.get(\n                        \"CAhandler\", \"dns_update_script_variables\", fallback=None\n                    )\n                )\n            except Exception as err:\n                self.logger.warning(\n                    \"CAhandler._config_dns_update_script_load(): Failed to parse dns_update_script_variables parameter: %s\",\n                    err,\n                )\n\n        self.logger.debug(\"CAhandler._config_dns_update_script_load() ended\")\n\n    def _config_profiles_load(self, config_dic: Dict[str, str]) -> Dict[str, str]:\n        \"\"\" \" load profiles from config file\"\"\"\n        self.logger.debug(\"CAhandler._config_profiles_load()\")\n        if \"CAhandler\" in config_dic and \"profiles_sync\" in config_dic[\"CAhandler\"]:\n            # load profiles from db if profiles_sync is set\n            try:\n                profiles_string = self.dbstore.hkparameter_get(\"profiles\")\n                profile_dic = json.loads(profiles_string)\n                profiles = profile_dic.get(\"profiles\", {})\n            except Exception as err:\n                self.logger.critical(\n                    \"Database error: failed to get profile list: %s\", err\n                )\n                profiles = {}\n        else:\n            # load profiles from config file\n            profiles = config_profile_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_profiles_load() ended\")\n        return profiles\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config()\n        if \"CAhandler\" in config_dic:\n\n            # load account configuration and paramters\n            self._config_account_load(config_dic)\n            self._config_parameters_load(config_dic)\n\n            self.logger.debug(\"CAhandler._config_load() ended\")\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"CAhandler\" section is missing in config file'\n            )\n\n        # load allowed domainlist\n        self.allowed_domainlist = config_allowed_domainlist_load(\n            self.logger, config_dic\n        )\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n        # load profiles\n        # self.profiles = config_profile_load(self.logger, config_dic)\n        self.profiles = self._config_profiles_load(config_dic)\n\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self._config_dns_update_script_load(config_dic)\n\n    def _challenge_filter(\n        self, authzr: messages.AuthorizationResource, chall_type: str = \"http-01\"\n    ) -> messages.ChallengeBody:\n        \"\"\"filter authorization for challenge\"\"\"\n        self.logger.debug(\"CAhandler._challenge_filter(%s)\", chall_type)\n        result = None\n        for challenge in authzr.body.challenges:\n            if challenge.chall.to_partial_json()[\"type\"] == chall_type:\n                result = challenge\n                break\n        if not result:\n            self.logger.error(\n                \"Could not find challenge of type %s\",\n                chall_type,\n            )\n\n        return result\n\n    def _challenge_info(\n        self, authzr: messages.AuthorizationResource, user_key: josepy.jwk.JWKRSA\n    ):\n        \"\"\"filter challenges and get challenge details\"\"\"\n        self.logger.debug(\"CAhandler._challenge_info()\")\n\n        chall_name = None\n        chall_content = None\n        challenge = None\n\n        if not authzr or not user_key:\n            if authzr:\n                self.logger.error(\"acme user is missing\")\n            else:\n                self.logger.error(\"acme authorization is missing\")\n            self.logger.debug(\"CAhandler._challenge_info() ended with %s\", chall_name)\n            return (chall_name, chall_content, challenge)\n\n        if self.dns_update_script:\n            chall_name, chall_content, challenge = self._get_dns_challenge(\n                authzr, user_key\n            )\n        else:\n            chall_name, chall_content, challenge = self._get_http_or_email_challenge(\n                authzr, user_key\n            )\n\n        self.logger.debug(\"CAhandler._challenge_info() ended with %s\", chall_name)\n        return (chall_name, chall_content, challenge)\n\n    def _dns_challenge_deprovision(self):\n        \"\"\"delete dns challenge\"\"\"\n        self.logger.debug(\"CAhandler._dns_challenge_deprovision()\")\n        if self.dns_update_script and self.acme_sh_script and self.dns_record_dic:\n\n            # get scriptname\n            basename_w_ext = os.path.splitext(os.path.basename(self.dns_update_script))[\n                0\n            ]\n\n            # set environment variables for dns update script\n            self._environment_variables_handle(unset=False)\n\n            for fqdn, txt_record_value in self.dns_record_dic.items():\n                # remove txt record from dns server - to be moved to a later place in the code\n                cmd_list = (\n                    f\"source {shlex.quote(self.acme_sh_script)} &>/dev/null; \"\n                    f\"source {shlex.quote(self.dns_update_script)}; \"\n                    f\"{shlex.quote(basename_w_ext)}_rm \"\n                    f\"{shlex.quote(fqdn)} \"\n                    f\"{shlex.quote(txt_record_value.decode('utf-8') if isinstance(txt_record_value, bytes) else str(txt_record_value))}\"\n                )\n                if self.acme_sh_shell:\n                    self.logger.debug(\n                        \"CAhandler._dns_challenge_provision(): using shell: %s\",\n                        self.acme_sh_shell,\n                    )\n                    rcode = subprocess.call(\n                        cmd_list, shell=True, executable=self.acme_sh_shell\n                    )\n                else:\n                    rcode = subprocess.call(cmd_list, shell=True)\n\n                self.logger.debug(\n                    \"_dns_challenge_deprovision(): %s rcode: %s\", fqdn, rcode\n                )\n\n            # unset environment variables for dns update script\n            self._environment_variables_handle(unset=True)\n\n    def _dns_challenge_provision(\n        self, fqdn: str, key_authorization: str, _user_key: josepy.jwk.JWKRSA\n    ) -> bool:\n        self.logger.debug(\"CAhandler._dns_challenge_provision(%s)\", fqdn)\n\n        # create txt record value\n        txt_record_value = b64_url_encode(\n            self.logger, sha256_hash(self.logger, key_authorization)\n        )\n\n        fqdn = f\"_acme-challenge.{fqdn}\"\n        self.logger.debug(\"fqdn: %s, txt_record_value: %s\", fqdn, txt_record_value)\n\n        basename_w_ext = os.path.splitext(os.path.basename(self.dns_update_script))[0]\n\n        # set environment variables for dns update script\n        self._environment_variables_handle(unset=False)\n\n        # add txt record to dns server\n        fqdn_escaped = shlex.quote(fqdn)\n        txt_record_value_str = (\n            txt_record_value.decode(\"utf-8\")\n            if isinstance(txt_record_value, bytes)\n            else str(txt_record_value)\n        )\n        txt_record_value_escaped = shlex.quote(txt_record_value_str)\n        acme_sh_script_escaped = shlex.quote(self.acme_sh_script)\n        dns_update_script_escaped = shlex.quote(self.dns_update_script)\n        basename_w_ext_escaped = shlex.quote(basename_w_ext)\n        cmd_list = f\"source {acme_sh_script_escaped} &>/dev/null; source {dns_update_script_escaped};  {basename_w_ext_escaped}_add {fqdn_escaped} {txt_record_value_escaped}\"  # noqa\n\n        if self.acme_sh_shell:\n            self.logger.debug(\n                \"CAhandler._dns_challenge_provision(): using shell: %s\",\n                self.acme_sh_shell,\n            )\n            rcode = subprocess.call(cmd_list, shell=True, executable=self.acme_sh_shell)\n        else:\n            rcode = subprocess.call(cmd_list, shell=True)\n\n        self.logger.debug(\"_dns_challenge_provision(): %s rcode: %s\", fqdn, rcode)\n\n        # unset environment variables for dns update script\n        self._environment_variables_handle(unset=True)\n\n        # store dns record in dictionary\n        # if rcode == 0:\n        self.dns_record_dic[fqdn] = txt_record_value\n\n        cnt = 0\n        query_record_value = None\n\n        # wait for dns update to be propagated\n        self.logger.debug(\n            \"CAhandler._dns_challenge_provision(): waiting 20s for dns update to be propagated\"\n        )\n        time.sleep(20)\n\n        if self.dns_validation_timeout - 20 > 0:\n            sleep_interval = (self.dns_validation_timeout - 20) / 10\n        else:\n            sleep_interval = 1\n        self.logger.debug(\n            \"CAhandler._dns_challenge_provision(): sleep_interval: %s\",\n            sleep_interval,\n        )\n        if self.dns_validation_timeout > 0:\n            while cnt <= 10:\n                # wait for dns update\n                time.sleep(sleep_interval)\n                query_record_value = txt_get(self.logger, fqdn)\n                self.logger.debug(\"%s txt_record_value: %s\", cnt, query_record_value)\n                cnt += 1\n                if query_record_value and txt_record_value in query_record_value:\n                    # stop waiting if we found the record in DNS\n                    self.logger.debug(\n                        \"_dns_challenge_provision(): found txt record in DNS\"\n                    )\n                    break\n\n    def _environment_variables_handle(self, unset=False):\n        \"\"\"set environment variables for dns update script\"\"\"\n        self.logger.debug(\"CAhandler._environment_variables_handle(): unset=%s\", unset)\n\n        forbidden_variables_list = [\n            \"SHELL\",\n            \"LANG\",\n            \"PATH\",\n            \"PWD\",\n            \"HOME\",\n            \"TZ\",\n            \"LD_PRELOAD\",\n            \"LD_LIBRARY_PATH\",\n            \"LD_AUDIT\",\n            \"LD_DEBUG\",\n            \"LD_DYNAMIC_WEAK\",\n            \"LD_BIND_NOW\",\n            \"LD_ORIGIN_PATH\",\n            \"LD_RUN_PATH\",\n            \"LD_ASSUME_KERNEL\",\n            \"LD_TRACE_LOADED_OBJECTS\",\n            \"LD_TRACE_PRELINKING\",\n            \"LD_USE_LOAD_BIAS\",\n            \"PYTHONPATH\",\n            \"PYTHONHOME\",\n            \"PYTHONUSERBASE\",\n        ]\n        for key, value in self.dns_update_script_variables.items():\n            if key not in forbidden_variables_list:\n                if unset:\n                    self.logger.debug(\n                        \"CAhandler._environment_variables_handle(): unsetting environment variable: %s\",\n                        key,\n                    )\n                    # unset environment variable\n                    if key in os.environ:\n                        del os.environ[key]\n                    else:\n                        self.logger.warning(\n                            'CAhandler._environment_variables_handle(): environment variable \"%s\" is not set and will not be unset',\n                            key,\n                        )\n                else:\n                    self.logger.debug(\n                        \"CAhandler._environment_variables_handle(): setting environment variable: %s=%s\",\n                        key,\n                        value,\n                    )\n                    os.environ[key] = value\n            else:\n                self.logger.warning(\n                    'CAhandler._environment_variables_handle(): environment variable \"%s\" is forbidden and will not be changed',\n                    key,\n                )\n\n    def _get_dns_challenge(self, authzr, user_key):\n        self.logger.debug(\"_get_dns_challenge()\")\n        challenge = self._challenge_filter(authzr, chall_type=\"dns-01\")\n        chall_name = None\n        chall_content = None\n        if challenge:\n            (\n                chall_content_obj,\n                _validation,\n            ) = challenge.chall.response_and_validation(user_key)\n            chall_content = chall_content_obj.key_authorization\n            chall_name = \"dns-challenge\"\n        return chall_name, chall_content, challenge\n\n    def _get_http_or_email_challenge(self, authzr, user_key):\n        self.logger.debug(\"CAhandler._get_http_or_email_challenge()\")\n        challenge = self._challenge_filter(authzr)\n        chall_name = None\n        chall_content = None\n        if challenge:\n            chall_content = challenge.chall.validation(user_key)\n            try:\n                (chall_name, _token) = chall_content.split(\".\", 2)\n            except Exception:\n                self.logger.error(\n                    \"Challenge split failed: %s\",\n                    chall_content,\n                )\n        else:\n            challenge = self._challenge_filter(authzr, chall_type=\"sectigo-email-01\")\n            if challenge:\n                chall_content = challenge.to_partial_json()\n        self.logger.debug(\"CAhandler._get_http_or_email_challenge() ended\")\n        return chall_name, chall_content, challenge\n\n    def _http_challenge_store(self, challenge_name: str, challenge_content: str):\n        \"\"\"store challenge into database\"\"\"\n        self.logger.debug(\"CAhandler._http_challenge_store(%s)\", challenge_name)\n\n        if challenge_name and challenge_content:\n            data_dic = {\"name\": challenge_name, \"value1\": challenge_content}\n            # store challenge into db\n            self.dbstore.cahandler_add(data_dic)\n        self.logger.debug(\"CAhandler._http_challenge_store() ended.\")\n\n    def _key_generate(self) -> josepy.jwk.JWKRSA:\n        \"\"\"generate key\"\"\"\n        self.logger.debug(\"CAhandler._key_generate(%s)\", self.key_size)\n        user_key = josepy.JWKRSA(\n            key=rsa.generate_private_key(\n                public_exponent=65537, key_size=self.key_size, backend=default_backend()\n            )\n        )\n        self.logger.debug(\"CAhandler._key_generate() ended.\")\n        return user_key\n\n    def _user_key_load(self) -> josepy.jwk.JWKRSA:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler._user_key_load(%s)\", self.acme_keyfile)\n\n        if os.path.exists(self.acme_keyfile):\n            self.logger.debug(\"CAhandler._user_key_load() opening user_key\")\n            with open(self.acme_keyfile, \"r\", encoding=\"utf8\") as keyf:\n\n                user_key_dic = json.loads(keyf.read())\n                # check if account_name is stored in keyfile\n                if \"account\" in user_key_dic:\n                    self.account = user_key_dic[\"account\"]\n                    self.logger.info(\"Account %s found in keyfile\", self.account)\n                    del user_key_dic[\"account\"]\n                user_key = josepy.JWKRSA.fields_from_json(user_key_dic)\n        else:\n            self.logger.debug(\"CAhandler._user_key_load() generate and register key\")\n            user_key = self._key_generate()\n            # dump keyfile to file\n            try:\n                with open(self.acme_keyfile, \"w\", encoding=\"utf8\") as keyf:\n                    keyf.write(json.dumps(user_key.to_json()))\n            except Exception as err:\n                self.logger.error(\"Error during key dumping: %s\", err)\n\n        self.logger.debug(\"CAhandler._user_key_load() ended with: %s\", bool(user_key))\n        return user_key\n\n    def _order_authorization(\n        self,\n        acmeclient: client.ClientV2,\n        order: messages.OrderResource,\n        user_key: josepy.jwk.JWKRSA,\n    ) -> bool:\n        \"\"\"validate challenges (refactored for clarity)\"\"\"\n        self.logger.debug(\"CAhandler._order_authorization()\")\n        authz_valid = False\n        for authzr in order.authorizations:\n            authz_valid = (\n                self._handle_authzr_status(acmeclient, authzr, user_key) or authz_valid\n            )\n        self.logger.debug(\n            \"CAhandler._order_authorization() ended with: %s\", authz_valid\n        )\n        return authz_valid\n\n    def _handle_authzr_status(self, acmeclient, authzr, user_key):\n        if authzr.body.status == messages.STATUS_PENDING:\n            return self._handle_pending_status(acmeclient, authzr, user_key)\n        elif authzr.body.status == messages.STATUS_VALID:\n            self.logger.info(\n                \"Authorization already valid. Skipping challenge validation.\"\n            )\n            return True\n        else:\n            self.logger.warning(\n                \"CAhandler._order_authorization(): authorization in unexpected state: %s\",\n                authzr.body.status,\n            )\n            return False\n\n    def _handle_pending_status(self, acmeclient, authzr, user_key):\n        challenge_name, challenge_content, challenge = self._challenge_info(\n            authzr, user_key\n        )\n        if challenge_name and challenge_content:\n            if self.dns_update_script and self.acme_sh_script:\n                self.logger.debug(\n                    \"CAhandler._order_authorization(): dns challenge detected\"\n                )\n                self._dns_challenge_provision(\n                    authzr.body.identifier.value, challenge_content, user_key\n                )\n            else:\n                self.logger.debug(\n                    \"CAhandler._order_authorization(): http challenge detected\"\n                )\n                self._http_challenge_store(challenge_name, challenge_content)\n            self.logger.debug(\"CAhandler._order_authorization(): answer challenge\")\n            _auth_response = acmeclient.answer_challenge(\n                challenge, challenge.chall.response(user_key)\n            )  # lgtm [py/unused-local-variable]\n            return True\n        elif (\n            isinstance(challenge_content, dict)\n            and challenge_content.get(\"type\", None) == \"sectigo-email-01\"\n            and challenge_content.get(\"status\", None) == \"valid\"\n        ):\n            self.logger.debug(\n                \"CAhandler._order_authorization(): sectigo-email-01 challenge detected\"\n            )\n            return True\n        return False\n\n    def _order_new(\n        self, acmeclient: client.ClientV2, csr_pem: str\n    ) -> messages.OrderResource:\n        \"\"\"create new order\"\"\"\n        self.logger.debug(\"CAhandler._order_new()\")\n\n        order = None\n        try:\n            if self.profile:\n                # profile is set\n                self.logger.debug(\n                    \"CAhandler._order_new() adding profile: %s\", self.profile\n                )\n                order = acmeclient.new_order(csr_pem=csr_pem, profile=self.profile)\n            else:\n                # no profile set\n                self.logger.debug(\"CAhandler._order_new() no profile set\")\n                order = acmeclient.new_order(csr_pem=csr_pem)\n        except Exception as err:\n            self.logger.warning(\n                \"Failed to create order: %s. Try without profile information.\",\n                err,\n            )\n            order = acmeclient.new_order(csr_pem=csr_pem)\n        self.logger.debug(\"CAhandler._order_new() ended with: %s\", bool(order))\n        return order\n\n    def _order_issue(\n        self, acmeclient: client.ClientV2, user_key: josepy.jwk.JWKRSA, csr_pem: str\n    ) -> Tuple[str, str, str]:\n        \"\"\"isuse order\"\"\"\n        self.logger.debug(\"CAhandler._order_issue() csr: \" + str(csr_pem))\n\n        # create new order\n        order = self._order_new(acmeclient, csr_pem)\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        # validate order\n        order_valid = self._order_authorization(acmeclient, order, user_key)\n\n        if order_valid:\n            self.logger.debug(\"CAhandler._order_issue() polling for certificate\")\n            order = acmeclient.poll_and_finalize(order)\n\n            if self.dns_update_script and self.acme_sh_script:\n                # delete dns-records\n                self._dns_challenge_deprovision()\n\n            if order.fullchain_pem:\n                self.logger.debug(\"CAhandler._order_issue() successful\")\n                cert_bundle = str(order.fullchain_pem)\n                # Split the chain into individual certificates\n                certs = cert_bundle.strip().split(\"-----END CERTIFICATE-----\")\n                # The first certificate is the end-entity certificate\n                cert_raw = b64_encode(\n                    self.logger, cert_pem2der(certs[0] + \"-----END CERTIFICATE-----\")\n                )\n            else:\n                self.logger.error(\"Error getting certificate: %s\", order.error)\n                error = f\"Error getting certificate: {order.error}\"\n        else:\n            self.logger.warning(\n                \"Order authorization failed. Challenges not answered correctly.\"\n            )\n            error = \"Order authorization failed. Challenges not answered correctly.\"\n\n        self.logger.debug(\"CAhandler._order_issue() ended\")\n        return (error, cert_bundle, cert_raw)\n\n    def _account_lookup(\n        self, acmeclient: client.ClientV2, reg: str, directory: messages.Directory\n    ):\n        \"\"\"lookup account\"\"\"\n        self.logger.debug(\"CAhandler._account_lookup()\")\n\n        response = acmeclient._post(directory[\"newAccount\"], reg)\n        regr = acmeclient._regr_from_response(response)\n        regr = acmeclient.query_registration(regr)\n        if regr:\n            self.logger.info(\"Found existing account: %s\", regr.uri)\n            self.account = regr.uri\n            if self.acme_url:\n                # remove url from string\n                self.account = self.account.replace(self.acme_url, \"\")\n            if \"acct_path\" in self.path_dic and self.path_dic[\"acct_path\"]:\n                # remove acc_path\n                self.account = self.account.replace(self.path_dic[\"acct_path\"], \"\")\n\n    def _jwk_strip(self, user_key: josepy.jwk.JWKRSA) -> josepy.jwk.JWKRSA:\n        \"\"\"\n        Returns a new josepy.jwk.JWKRSA object containing only the minimal required fields (kty, n, e).\n        \"\"\"\n        self.logger.debug(\"CAhandler._jwk_strip()\")\n\n        # Extract the minimal JWK dict\n        full_jwk = user_key.to_json()\n        if \"kty\" in full_jwk and full_jwk[\"kty\"] == \"RSA\":\n            self.logger.debug(\"Stripping JWK to minimal fields for RSA key\")\n            required_fields = (\"kty\", \"n\", \"e\")\n            missing_fields = [k for k in required_fields if k not in full_jwk]\n            if missing_fields:\n                self.logger.error(\n                    f\"Missing required JWK fields for RSA key: {', '.join(missing_fields)}\"\n                )\n                return None\n            minimal_jwk = {k: full_jwk[k] for k in required_fields}\n            # Reconstruct a JWKRSA object from the minimal dict\n            try:\n                result = josepy.JWKRSA.fields_from_json(minimal_jwk)\n            except Exception as e:\n                self.logger.error(\n                    \"Failed to strip JWK to minimal fields. Input: %s, Error: %s\",\n                    minimal_jwk,\n                    str(e),\n                )\n                result = None\n        else:\n            result = user_key\n        self.logger.debug(\"CAhandler._jwk_strip() ended\")\n        return result\n\n    def _account_create(\n        self,\n        acmeclient: client.ClientV2,\n        user_key: josepy.jwk.JWKRSA,\n        directory: messages.Directory,\n    ) -> messages.RegistrationResource:\n        \"\"\"register account\"\"\"\n        self.logger.debug(\n            \"CAhandler._account_create(): register new account with email: %s\",\n            self.email,\n        )\n\n        regr = None\n        if self.email:\n            self.logger.debug(\n                \"CAhandler._account_create(): register new account with email: %s\",\n                self.email,\n            )\n            if (\n                self.acme_url\n                and \"host\" in self.acme_url_dic\n                and (\n                    self.acme_url_dic[\"host\"] == \"zerossl.com\"\n                    or self.acme_url_dic[\"host\"].endswith(\".zerossl.com\")\n                )\n            ):  # lgtm [py/incomplete-url-substring-sanitization]\n                # get zerossl eab credentials\n                self._zerossl_eab_get()\n            if self.eab_kid and self.eab_hmac_key:\n                # use EAB credentials for registration\n                self.logger.info(\n                    \"Using EAB key_id: %s for account registration\", self.eab_kid\n                )\n                user_key = self._jwk_strip(user_key)\n                eab = messages.ExternalAccountBinding.from_data(\n                    account_public_key=user_key,\n                    kid=self.eab_kid,\n                    hmac_key=self.eab_hmac_key,\n                    directory=directory,\n                )\n                reg = messages.NewRegistration.from_data(\n                    key=user_key,\n                    email=self.email,\n                    terms_of_service_agreed=True,\n                    external_account_binding=eab,\n                )\n            else:\n                # register with email\n                reg = messages.NewRegistration.from_data(\n                    key=user_key, email=self.email, terms_of_service_agreed=True\n                )\n            try:\n                regr = acmeclient.new_account(reg)\n                self.logger.debug(\n                    \"CAhandler._account_create(): new account reqistered.\"\n                )\n            except errors.ConflictError:\n                self.logger.error(\n                    \"Account registration failed: ConflictError\"\n                )  # pragma: no cover\n            except Exception as err:\n                self.logger.error(\"Account registration failed: %s\", err)\n        else:\n            self.logger.error(\"Registration aborted. Email address is missing\")\n\n        self.logger.debug(\"CAhandler._account_create() ended with: %s\", bool(regr))\n        return regr\n\n    def _accountname_get(\n        self, url: str, acme_url: str, path_dic: Dict[str, str]\n    ) -> str:\n        \"\"\"get accountname from url\"\"\"\n        self.logger.debug(\"CAhandler._accountname_get()\")\n\n        account = None\n\n        acct_path = path_dic.get(\"acct_path\", None)\n        if acct_path == \"/\":\n            # remove url from string\n            account = url.replace(acme_url, \"\").lstrip(\"/\")\n        elif acct_path:\n            # remove url from string\n            account = url.replace(acme_url, \"\").replace(path_dic[\"acct_path\"], \"\")\n        else:\n            account = url.replace(acme_url, \"\")\n\n        self.logger.debug(\"CAhandler._accountname_get() ended with: %s\", account)\n        return account\n\n    def _account_register(\n        self,\n        acmeclient: client.ClientV2,\n        user_key: josepy.jwk.JWKRSA,\n        directory: messages.Directory,\n    ) -> messages.RegistrationResource:\n        \"\"\"register account / check registration\"\"\"\n        self.logger.debug(\"CAhandler._account_register(%s)\", self.email)\n\n        try:\n            # we assume that the account exist and need to query the account id\n            reg = messages.NewRegistration.from_data(\n                key=user_key,\n                email=self.email,\n                terms_of_service_agreed=True,\n                only_return_existing=True,\n            )\n            response = acmeclient._post(directory[\"newAccount\"], reg)\n            regr = acmeclient._regr_from_response(response)\n            regr = acmeclient.query_registration(regr)\n            if hasattr(regr, \"uri\"):\n                self.logger.debug(\n                    \"CAhandler.__account_register(): found existing account: %s\",\n                    regr.uri,\n                )\n        except Exception:\n            regr = self._account_create(acmeclient, user_key, directory)\n\n        if regr:\n            # extract the account-name from registration ressource\n            if self.acme_url and \"acct_path\" in self.path_dic:\n                if hasattr(regr, \"uri\"):\n                    self.account = self._accountname_get(\n                        regr.uri, self.acme_url, self.path_dic\n                    )\n\n            if self.account:\n                self.logger.info(\n                    \"acme-account id is %s. Please add an corresponding acme_account parameter to your acme_srv.cfg to avoid unnecessary lookups\",\n                    self.account,\n                )\n                self._account_to_keyfile()\n\n        else:\n            self.logger.error(\"Registration failed\")\n        return regr\n\n    def _account_to_keyfile(self):\n        \"\"\"add account to keyfile\"\"\"\n        self.logger.debug(\"CAhandler._account_to_keyfile()\")\n\n        if self.acme_keyfile and self.account:\n            try:\n                with open(self.acme_keyfile, \"r\", encoding=\"utf8\") as keyf:\n                    key_dic = json.loads(keyf.read())\n                    key_dic[\"account\"] = self.account\n\n                with open(self.acme_keyfile, \"w\", encoding=\"utf8\") as keyf:\n                    keyf.write(json.dumps(key_dic))\n            except Exception as err:\n                self.logger.error(\"Could not map account to keyfile: %s\", err)\n\n    def _zerossl_eab_get(self):\n        \"\"\"get eab credentials from zerossl\"\"\"\n        self.logger.debug(\"CAhandler._zerossl_eab_get()\")\n\n        zero_eab_email = \"http://api.zerossl.com/acme/eab-credentials-email\"\n        data = {\"email\": self.email}\n\n        response = requests.post(zero_eab_email, data=data, timeout=20)\n        if (\n            \"success\" in response.json()\n            and response.json()[\"success\"]\n            and \"eab_kid\" in response.json()\n            and \"eab_hmac_key\" in response.json()\n        ):\n            self.eab_kid = response.json()[\"eab_kid\"]\n            self.eab_hmac_key = response.json()[\"eab_hmac_key\"]\n            self.logger.debug(\"CAhandler._zerossl_eab_get() ended successfully\")\n        else:\n            self.logger.error(\n                \"Could not get eab credentials from ZeroSSL: %s\", response.text\n            )\n\n    def _eab_profile_list_set(self, csr: str, key: str, value: str) -> str:\n        self.logger.debug(\n            \"CAhandler._acme_keyfile_set(): list: key: %s, value: %s\", key, value\n        )\n\n        result = None\n        new_value, error = client_parameter_validate(self.logger, csr, self, key, value)\n        if new_value:\n            self.logger.debug(\n                \"CAhandler._eab_profile_list_set(): setting attribute: %s to %s\",\n                key,\n                new_value,\n            )\n            setattr(self, key, new_value)\n            if key == \"acme_url\":\n                if not self.acme_keypath:\n                    result = \"acme_keypath is missing in config\"\n                    self.logger.error(\"acme_keypath is missing in config\")\n                else:\n                    self.acme_url_dic = parse_url(self.logger, new_value)\n                    self.acme_keyfile = f\"{self.acme_keypath.rstrip('/')}/{self.acme_url_dic['host'].replace(':', '.')}.json\"\n        else:\n            result = error\n\n        return result\n\n    def eab_profile_list_check(\n        self, eab_handler: str, csr: str, key: str, value: str\n    ) -> str:\n        \"\"\"check eab profile list\"\"\"\n        self.logger.debug(\n            \"CAhandler._eab_profile_list_check(): list: key: %s, value: %s\", key, value\n        )\n\n        result = None\n        if hasattr(self, key) and key != \"allowed_domainlist\":\n            if key == \"acme_keyfile\":\n                self.logger.error(\"acme_keyfile is not allowed in profile\")\n            else:\n                result = self._eab_profile_list_set(csr, key, value)\n\n        elif key == \"allowed_domainlist\":\n            # check if csr contains allowed domains\n            if \"allowed_domains_check\" in dir(eab_handler):\n                # execute a function from eab_handler\n                self.logger.info(\"Execute allowed_domains_check() from eab handler\")\n                error = eab_handler.allowed_domains_check(csr, value)\n            else:\n                # execute default adl function from helper\n                self.logger.debug(\n                    \"Helper.eab_profile_list_check(): execute default allowed_domainlist_check()\"\n                )\n                error = allowed_domainlist_check(self.logger, csr, value)\n            if error:\n                result = error\n        else:\n            self.logger.error(\n                \"handler specific EAB profile list checking: ignore list attribute: key: %s value: %s\",\n                key,\n                value,\n            )\n\n        self.logger.debug(\"CAhandler._eab_profile_list_check() ended with: %s\", result)\n        return result\n\n    def _enroll(\n        self,\n        acmeclient: client.ClientV2,\n        user_key: josepy.jwk.JWKRSA,\n        csr_pem: str,\n        regr: messages.RegistrationResource,\n    ) -> Tuple[str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        if regr.body.status == \"valid\":\n            self.logger.debug(\"CAhandler._enroll(): Valid ACME account: %s\", regr.uri)\n            (error, cert_bundle, cert_raw) = self._order_issue(\n                acmeclient, user_key, csr_pem\n            )\n        elif not regr.body.status and regr.uri:\n            # this is an exisitng but not configured account. Throw error but continue enrolling\n            self.logger.info(\"Existing but not configured ACME account: %s\", regr.uri)\n            (error, cert_bundle, cert_raw) = self._order_issue(\n                acmeclient, user_key, csr_pem\n            )\n        else:\n            self.logger.error(\n                \"Enrollment failed: Bad ACME account: %s\", regr.body.error\n            )\n            error = f\"Bad ACME account: {regr.body.error}\"\n\n        self.logger.debug(\"CAhandler._enroll() ended with %s\", bool(cert_raw))\n        return error, cert_bundle, cert_raw\n\n    def _registration_lookup(\n        self,\n        acmeclient: client.ClientV2,\n        reg: messages.Registration,\n        directory: messages.Directory,\n        user_key,\n    ) -> messages.RegistrationResource:\n        \"\"\"lookup registration\"\"\"\n        self.logger.debug(\"CAhandler._registration_lookup()\")\n\n        if self.account:\n            regr = messages.RegistrationResource(\n                uri=f\"{self.acme_url}{self.path_dic['acct_path']}{self.account}\",\n                body=reg,\n            )\n            self.logger.debug(\n                \"CAhandler._registration_lookup(): checking remote registration status\"\n            )\n            regr = acmeclient.query_registration(regr)\n            if hasattr(regr, \"uri\"):\n                self.logger.info(\n                    \"Found existing account: %s\",\n                    regr.uri,\n                )\n            else:\n                self.logger.error(\n                    \"Account lookup failed. Account %s not found. Trying to register new account.\",\n                    self.account,\n                )\n                regr = self._account_register(acmeclient, user_key, directory)\n                if hasattr(regr, \"uri\"):\n                    self.logger.info(\"New account: %s\", regr.uri)\n        else:\n            # new account or existing account with missing account id\n            regr = self._account_register(acmeclient, user_key, directory)\n            if hasattr(regr, \"uri\"):\n                self.logger.info(\"New account: %s\", regr.uri)\n\n        self.logger.debug(\"CAhandler._registration_lookup() ended with: %s\", bool(regr))\n        return regr\n\n    def _revoke_or_fallback(self, acmeclient=None, cert: str = None):\n        \"\"\"revoke certificate or fallback to pre-4.0 method\"\"\"\n        self.logger.debug(\"CAhandler._revoke_or_fallback()\")\n\n        try:\n            cert_obj = x509.load_der_x509_certificate(\n                b64_url_decode(self.logger, cert), backend=default_backend()\n            )\n            acmeclient.revoke(cert_obj, 1)\n        except Exception as err:\n            self.logger.error(\n                \"Revocation error: %s. Fallback to pre-4.0 method\",\n                err,\n            )\n            cert_obj = josepy.ComparableX509(\n                crypto.load_certificate(\n                    crypto.FILETYPE_ASN1,\n                    b64_url_decode(self.logger, cert),\n                )\n            )\n            acmeclient.revoke(cert_obj, 1)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        # pylint: disable=R0915\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        csr_pem = f\"-----BEGIN CERTIFICATE REQUEST-----\\n{textwrap.fill(str(b64_url_recode(self.logger, csr)), 64)}\\n-----END CERTIFICATE REQUEST-----\\n\".encode(\n            \"utf-8\"\n        )  # noqa\n\n        cert_bundle = None\n        cert_raw = None\n        poll_indentifier = None\n        user_key = None\n\n        error = allowed_domainlist_check(self.logger, csr, self.allowed_domainlist)\n\n        # check for eab profiling and header_info\n        if not error:\n            error = eab_profile_header_info_check(self.logger, self, csr, \"profile\")\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"dbstore\", \"eab_mac_key\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if not error:\n            try:\n                user_key = self._user_key_load()\n                net = client.ClientNetwork(user_key, verify_ssl=self.ssl_verify)\n\n                directory = messages.Directory.from_json(\n                    net.get(f'{self.acme_url}{self.path_dic[\"directory_path\"]}').json()\n                )\n                acmeclient = client.ClientV2(directory, net=net)\n                reg = messages.Registration.from_data(\n                    key=user_key, terms_of_service_agreed=True\n                )\n\n                # lookup account / create new account\n                regr = self._registration_lookup(acmeclient, reg, directory, user_key)\n                if regr:\n                    # enroll certificate\n                    error, cert_bundle, cert_raw = self._enroll(\n                        acmeclient, user_key, csr_pem, regr\n                    )\n                else:\n                    self.logger.error(\"Account registration failed\")\n                    error = \"Account registration failed\"\n            except Exception as err:\n                self.logger.error(\"Enrollment error: %s\", err)\n                error = str(err)\n            finally:\n                del user_key\n        else:\n            self.logger.error(\"Enrollment error: CSR rejected. %s\", error)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        error = handler_config_check(self.logger, self, [\"acme_url\", \"email\"])\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def _synchronize_profiles(self, repository: object, acme_url: str, uts: int):\n        \"\"\"synchronize profiles with CA\"\"\"\n        self.logger.debug(\"CAhandler.synchronize_profiles()\")\n\n        result, code, error = url_get(\n            self.logger, acme_url + self.path_dic[\"directory_path\"], timeout=5\n        )\n        if code == 200:\n            json_data = json.loads(result)\n            profiles = json.dumps(\n                {\n                    \"profiles\": json_data.get(\"meta\").get(\"profiles\", {}),\n                    \"synchronized_at\": uts,\n                }\n            )\n            data_dic = {\"name\": \"profiles\", \"value\": profiles}\n            repository.profile_list_set(data_dic)\n        else:\n            self.logger.error(\"Error during profile synchronization: %s\", error)\n\n        self.logger.debug(\"CAhandler.synchronize_profiles() ended\")\n\n    def synchronize_profiles(\n        self,\n        repository: object,\n        acme_url: str,\n        profiles_sync_interval: int,\n        async_mode: bool = False,\n    ) -> Dict[str, str]:\n        \"\"\"synchronize profiles with CA\"\"\"\n        self.logger.debug(\"CAhandler.synchronize_profiles()\")\n\n        uts = uts_now()\n        profiles_dic = repository.profile_list_get()\n\n        if not profiles_dic or (\n            \"synchronized_at\" in profiles_dic\n            and int(profiles_dic[\"synchronized_at\"]) + profiles_sync_interval < uts\n        ):\n            # profile does not exist or is outdated\n            self.logger.info(\"CA profiles outdated. Synchronize from acme_server\")\n            # start profile update in separate thread\n            twrv = Thread(target=self._synchronize_profiles(repository, acme_url, uts))\n            twrv.start()\n            if async_mode:\n                # full async mode - do not wait for result\n                self.logger.debug(\n                    \"CAhandler.synchronize_profiles(): asynchronous processing enabled, not waiting for result\"\n                )\n            else:\n                twrv.join(timeout=2)\n\n        else:\n            self.logger.debug(\n                \"CAhandler.synchronize_profiles(): valid profile information found in repository. Skipping syncronization.\"\n            )\n\n        profiles = profiles_dic.get(\"profiles\", {}) if profiles_dic else {}\n\n        self.logger.debug(\"CAhandler.synchronize_profiles() ended\")\n        return profiles\n\n    def _get_renewalinfo_endpoint_url(self, acme_url: str) -> str:\n        \"\"\"get renewalinfo endpoint url\"\"\"\n        self.logger.debug(\"CAhandler._get_renewalinfo_endpoint_url()\")\n\n        renewalinfo_enpoint_url = f\"{acme_url}/renewal-info\"  # default fallback\n\n        try:\n            response = url_get(\n                self.logger, f\"{acme_url}{self.path_dic['directory_path']}\", timeout=10\n            )\n            if (\n                isinstance(response, (list, tuple))\n                and len(response) >= 2\n                and response[1] == 200\n            ):\n                try:\n                    directory_dic = json.loads(response[0])\n                    if (\n                        isinstance(directory_dic, dict)\n                        and \"renewalInfo\" in directory_dic\n                    ):\n                        self.logger.debug(\n                            \"CAhandler._get_renewalinfo_endpoint_url(): using renewalInfo from directory\"\n                        )\n                        renewalinfo_enpoint_url = directory_dic[\"renewalInfo\"]\n                    else:\n                        self.logger.debug(\n                            \"CAhandler._get_renewalinfo_endpoint_url(): renewalInfo not found in directory, using default path\"\n                        )\n                except Exception as e:\n                    self.logger.error(\"Failed to parse directory JSON: %s\", e)\n            else:\n                self.logger.warning(\n                    \"Failed to fetch directory or unexpected response: %s\", response\n                )\n        except Exception as e:\n            self.logger.error(\"Exception in _get_renewalinfo_endpoint_url: %s\", e)\n\n        self.logger.debug(\n            \"CAhandler._get_renewalinfo_endpoint_url() ended with: %s\",\n            renewalinfo_enpoint_url,\n        )\n        return renewalinfo_enpoint_url\n\n    def lookup_renewalinfo(\n        self, acme_url, renewalinfo_string: str\n    ) -> Tuple[str, Dict[str, str]]:\n        \"\"\"lookup renewalinfo and return cert and csr\"\"\"\n        self.logger.debug(\"CAhandler.lookup_renewalinfo()\")\n\n        renewal_enpoint_url = self._get_renewalinfo_endpoint_url(acme_url)\n        url = f\"{renewal_enpoint_url}/{renewalinfo_string}\"\n        rcode = 500\n        renewalinfo_dic = {}\n\n        try:\n            ca_renewal_string = url_get(self.logger, url, timeout=10)\n            if (\n                isinstance(ca_renewal_string, (list, tuple))\n                and len(ca_renewal_string) >= 2\n            ):\n                try:\n                    renewalinfo_dic = json.loads(ca_renewal_string[0])\n                    rcode = ca_renewal_string[1]\n                except Exception as err:\n                    self.logger.error(\"Error decoding renewalinfo JSON: %s\", err)\n                    renewalinfo_dic = {}\n                    rcode = 500\n            else:\n                self.logger.error(\n                    \"Unexpected response from url_get: %s\", ca_renewal_string\n                )\n        except Exception as err:\n            self.logger.error(\"Error during renewalinfo lookup: %s\", err)\n            renewalinfo_dic = {}\n            rcode = 400\n\n        self.logger.debug(\"CAhandler.lookup_renewalinfo() ended\")\n        return (rcode, renewalinfo_dic)\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Not implemented\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        _cert: str,\n        _rev_reason: str = \"unspecified\",\n        _rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        user_key = None\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = None\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, _cert)\n\n        try:\n            if os.path.exists(self.acme_keyfile):\n                user_key = self._user_key_load()\n\n            if user_key:\n                net = client.ClientNetwork(user_key)\n\n                directory = messages.Directory.from_json(\n                    net.get(f\"{self.acme_url}{self.path_dic['directory_path']}\").json()\n                )\n                acmeclient = client.ClientV2(directory, net=net)\n\n                reg = messages.NewRegistration.from_data(\n                    key=user_key,\n                    email=self.email,\n                    terms_of_service_agreed=True,\n                    only_return_existing=True,\n                )\n\n                if not self.account:\n                    self._account_lookup(acmeclient, reg, directory)\n\n                if self.account:\n                    regr = messages.RegistrationResource(\n                        uri=f\"{self.acme_url}{self.path_dic['acct_path']}{self.account}\",\n                        body=reg,\n                    )\n                    self.logger.debug(\n                        \"CAhandler.revoke() checking remote registration status\"\n                    )\n                    regr = acmeclient.query_registration(regr)\n\n                    if regr.body.status == \"valid\":\n                        self.logger.debug(\"CAhandler.revoke() issuing revocation order\")\n                        # revoke certificate\n                        self._revoke_or_fallback(acmeclient, _cert)\n                        self.logger.debug(\"CAhandler.revoke() successful\")\n                        code = 200\n                        message = None\n                    else:\n                        self.logger.error(\n                            \"Enrollment error: Bad ACME account: %s\", regr.body.error\n                        )\n                        detail = f\"Bad ACME account: {regr.body.error}\"\n\n                else:\n                    self.logger.error(\n                        \"Error during revocation operation. Could not find account key and lookup at acme-endpoint failed.\"\n                    )\n                    detail = \"account lookup failed\"\n            else:\n                self.logger.error(\n                    \"Error during revocation: Could not load user_key %s\",\n                    self.acme_keyfile,\n                )\n                detail = \"Internal Error\"\n\n        except Exception as err:\n            self.logger.error(\"Revocation error: %s\", err)\n            detail = str(err)\n\n        finally:\n            del user_key\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[int, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Not implemented\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/asa_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Insta Active Security API  handler\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple, Dict\nimport os\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    load_config,\n    encode_url,\n    csr_pubkey_get,\n    csr_cn_get,\n    csr_san_get,\n    uts_now,\n    uts_to_date_utc,\n    b64_decode,\n    cert_der2pem,\n    convert_byte_to_string,\n    cert_ski_get,\n    config_eab_profile_load,\n    config_headerinfo_load,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    config_enroll_config_log_load,\n    config_profile_load,\n    enrollment_config_log,\n    handler_config_check,\n)\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.api_host = None\n        self.api_user = None\n        self.api_password = None\n        self.api_key = None\n        self.ca_bundle = None\n        self.proxy = None\n        self.request_timeout = 10\n        self.ca_name = None\n        self.auth = None\n        self.profile_name = None\n        self.cert_validity_days = 30\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.api_host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _api_get(self, url: str) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_get()\")\n        headers = {\"x-api-key\": self.api_key}\n\n        try:\n            api_response = requests.get(\n                url=url,\n                headers=headers,\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            )\n            code = api_response.status_code\n            try:\n                content = api_response.json()\n            except Exception as err_:\n                self.logger.error(\n                    \"Could not parse the response for an API get() request: %s\", err_\n                )\n                content = str(err_)\n        except Exception as err_:\n            self.logger.error(\"API get() request returned error: %s\", err_)\n            code = 500\n            content = str(err_)\n\n        return code, content\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_post()\")\n        headers = {\"x-api-key\": self.api_key}\n\n        try:\n            api_response = requests.post(\n                url=url,\n                headers=headers,\n                json=data,\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            )\n            code = api_response.status_code\n            if api_response.text:\n                try:\n                    content = api_response.json()\n                except Exception as err_:\n                    self.logger.error(\n                        \"Could not parse the response for an API post() request: %s\",\n                        err_,\n                    )\n                    content = str(err_)\n            else:\n                content = None\n        except Exception as err_:\n            self.logger.error(\"API post() request returned an error: %s\", err_)\n            code = 500\n            content = str(err_)\n\n        return code, content\n\n    def _auth_set(self):\n        \"\"\"set basic authentication header\"\"\"\n        self.logger.debug(\"CAhandler._auth_set()\")\n        if self.api_user and self.api_password:\n            self.auth = HTTPBasicAuth(self.api_user, self.api_password)\n        else:\n            self.logger.error(\n                'Auth information incomplete. Either \"api_user\" or \"api_password\" parameter is missing in config file'\n            )\n        self.logger.debug(\"CAhandler._auth_set() ended\")\n\n    def _config_host_load(self, config_dic: Dict[str, str]):\n        \"\"\"load hostname\"\"\"\n        self.logger.debug(\"_config_host_load()\")\n\n        api_host_variable = config_dic.get(\"api_host_variable\")\n        if api_host_variable:\n            self.api_host = os.environ.get(api_host_variable)\n            if not self.api_host:\n                self.logger.error(f\"Could not load host_variable: {api_host_variable}\")\n\n        api_host = config_dic.get(\"api_host\")\n        if api_host:\n            if self.api_host:\n                self.logger.info(\"Overwrite api_host parameter\")\n            self.api_host = api_host\n\n        self.logger.debug(\"_config_host_load() ended\")\n\n    def _certificates_list(self) -> Dict[str, str]:\n        \"\"\"list profiles\"\"\"\n        self.logger.debug(\"CAhandler._certificates_list()\")\n\n        url = f\"{self.api_host}/list_certificates?issuerName={encode_url(self.logger, self.ca_name)}\"\n        _code, api_response = self._api_get(url)\n\n        self.logger.debug(\"CAhandler._certificates_list() ended\")\n        return api_response\n\n    def _config_key_load(self, config_dic: Dict[str, str]):\n        \"\"\"load keyname\"\"\"\n        self.logger.debug(\"_config_key_load()\")\n\n        api_key_variable = config_dic.get(\"api_key_variable\")\n        if api_key_variable:\n            self.api_key = os.environ.get(api_key_variable)\n            if not self.api_key:\n                self.logger.error(f\"Could not load key_variable: {api_key_variable}\")\n\n        api_key = config_dic.get(\"api_key\")\n        if api_key:\n            if self.api_key:\n                self.logger.info(\"Overwrite api_key parameter\")\n            self.api_key = api_key\n\n        self.logger.debug(\"_config_key_load() ended\")\n\n    def _config_password_load(self, config_dic: Dict[str, str]):\n        \"\"\"load passwordname\"\"\"\n        self.logger.debug(\"_config_password_load()\")\n\n        api_password_variable = config_dic.get(\"api_password_variable\")\n        if api_password_variable:\n            self.api_password = os.environ.get(api_password_variable)\n            if not self.api_password:\n                self.logger.error(\n                    f\"Could not load password_variable: {api_password_variable}\"\n                )\n\n        api_password = config_dic.get(\"api_password\")\n        if api_password:\n            if self.api_password:\n                self.logger.info(\"Overwrite api_password parameter\")\n            self.api_password = api_password\n\n        self.logger.debug(\"_config_password_load() ended\")\n\n    def _config_user_load(self, config_dic: Dict[str, str]):\n        \"\"\"load username\"\"\"\n        self.logger.debug(\"_config_user_load()\")\n\n        api_user_variable = config_dic.get(\"api_user_variable\")\n        if api_user_variable:\n            self.api_user = os.environ.get(api_user_variable)\n            if not self.api_user:\n                self.logger.error(f\"Could not load user_variable: {api_user_variable}\")\n\n        api_user = config_dic.get(\"api_user\")\n        if api_user:\n            if self.api_user:\n                self.logger.info(\"Overwrite api_user parameter\")\n            self.api_user = api_user\n\n        self.logger.debug(\"_config_user_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n            self._config_host_load(config_dic[\"CAhandler\"])\n            self._config_user_load(config_dic[\"CAhandler\"])\n            self._config_password_load(config_dic[\"CAhandler\"])\n            self._config_key_load(config_dic[\"CAhandler\"])\n            self.ca_name = config_dic[\"CAhandler\"].get(\"ca_name\")\n            self.profile_name = config_dic[\"CAhandler\"].get(\"profile_name\")\n\n            if (\n                \"ca_bundle\" in config_dic[\"CAhandler\"]\n                and config_dic[\"CAhandler\"][\"ca_bundle\"] == \"False\"\n            ):\n                self.ca_bundle = False\n            else:\n                self.ca_bundle = config_dic[\"CAhandler\"].get(\"ca_bundle\")\n\n            try:\n                self.request_timeout = int(\n                    config_dic[\"CAhandler\"].get(\"request_timeout\", 10)\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"request_timeout parameter is not an integer. Error: %s\", err\n                )\n\n            try:\n                self.cert_validity_days = int(\n                    config_dic[\"CAhandler\"].get(\"cert_validity_days\", 30)\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"cert_validity_days parameter is not an integer. Error: %s\", err\n                )\n\n        for ele in [\n            \"api_host\",\n            \"api_user\",\n            \"api_password\",\n            \"api_key\",\n            \"ca_name\",\n            \"profile_name\",\n        ]:\n            if not getattr(self, ele):\n                self.logger.error(\n                    \"Configuration incomplete. Variable %s has not been not set\", ele\n                )\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n\n        self._auth_set()\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _csr_cn_get(self, csr: str) -> str:\n        \"\"\"get CN from csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_cn_get()\")\n\n        cn = csr_cn_get(self.logger, csr)\n\n        if not cn:\n            self.logger.info(\"CN not found in CSR\")\n            san_list = csr_san_get(self.logger, csr)\n            if san_list:\n                (_type, san_value) = san_list[0].split(\":\")\n                cn = san_value\n                self.logger.info(\n                    \"CN not found in CSR. Using first SAN entry as CN: %s\",\n                    san_value,\n                )\n            else:\n                self.logger.error(\"CN not found in CSR. No SAN entries found\")\n\n        self.logger.debug(\"CAhandler._csr_cn_get() ended with: %s\", cn)\n        return cn\n\n    def _issuer_verify(self) -> str:\n        \"\"\"verify issuer\"\"\"\n        self.logger.debug(\"CAhandler._issuer_verify()\")\n\n        api_response = self._issuers_list()\n\n        if \"issuers\" in api_response:\n            if self.ca_name in api_response[\"issuers\"]:\n                error = None\n            else:\n                error = f\"CA {self.ca_name} not found\"\n                self.logger.error(\"CAhandler.enroll(): CA %s not found\", self.ca_name)\n        else:\n            error = \"Malformed response\"\n            self.logger.error('Malformed response. \"issuers\" key not found')\n\n        self.logger.debug(\"CAhandler._issuer_verify() ended with: %s\", error)\n        return error\n\n    def _issuers_list(self) -> Dict[str, str]:\n        \"\"\"list issuers\"\"\"\n        self.logger.debug(\"CAhandler._list_issuers()\")\n\n        url = f\"{self.api_host}/list_issuers\"\n        _code, api_response = self._api_get(url)\n\n        self.logger.debug(\"CAhandler._list_issuers() ended\")\n        return api_response\n\n    def _profiles_list(self) -> Dict[str, str]:\n        \"\"\"list profiles\"\"\"\n        self.logger.debug(\"CAhandler._profiles_list()\")\n\n        url = f\"{self.api_host}/list_profiles?issuerName={encode_url(self.logger, self.ca_name)}\"\n        _code, api_response = self._api_get(url)\n\n        self.logger.debug(\"CAhandler._profiles_list() ended\")\n        return api_response\n\n    def _profile_verify(self) -> str:\n        \"\"\"verify profile\"\"\"\n        self.logger.debug(\"CAhandler._profile_verify(%s)\", self.profile_name)\n        api_response = self._profiles_list()\n\n        if \"profiles\" in api_response:\n            if self.profile_name in api_response[\"profiles\"]:\n                error = None\n            else:\n                error = f\"Profile {self.profile_name} not found\"\n                self.logger.error(\"Profile %s not found\", self.profile_name)\n        else:\n            error = \"Malformed response\"\n            self.logger.error('Malformed response. \"profiles\" key not found')\n\n        self.logger.debug(\"CAhandler._profile_verify() ended with: %s\", error)\n        return error\n\n    def _validity_dates_get(self) -> Tuple[str, str]:\n        \"\"\"calculate validity dates\"\"\"\n        self.logger.debug(\"CAhandler._validity_dates_get()\")\n\n        uts_now_ = uts_now()\n        validfrom = uts_to_date_utc(uts_now_, tformat=\"%Y-%m-%dT%H:%M:%S\")\n        validto = uts_to_date_utc(\n            uts_now_ + (self.cert_validity_days * 24 * 60 * 60),\n            tformat=\"%Y-%m-%dT%H:%M:%S\",\n        )\n\n        self.logger.debug(\"CAhandler._validity_dates_get() ended\")\n        return validfrom, validto\n\n    def _pem_cert_chain_generate(self, certs_list: list) -> str:\n        \"\"\"generate PEM certificate chain\"\"\"\n        self.logger.debug(\"CAhandler._pem_cert_chain_generate()\")\n\n        pem_chain = \"\"\n        for cert in certs_list:\n            pem_chain += convert_byte_to_string(\n                cert_der2pem(b64_decode(self.logger, cert))\n            )\n\n        self.logger.debug(\"CAhandler._pem_cert_chain_generate() ended\")\n        return pem_chain\n\n    def _issuer_chain_get(self) -> str:\n        \"\"\"get issuer chain\"\"\"\n        self.logger.debug(\"CAhandler._issuer_chain_get()\")\n\n        url = f\"{self.api_host}/get_issuer_chain?issuerName={encode_url(self.logger, self.ca_name)}\"\n        _code, api_response = self._api_get(url)\n        if \"certs\" in api_response:\n            pem_chain = self._pem_cert_chain_generate(api_response[\"certs\"])\n        else:\n            self.logger.error('\"certs\" key in issuer chain not found')\n            pem_chain = None\n\n        self.logger.debug(\"CAhandler._issuer_chain_get() ended\")\n        return pem_chain\n\n    def _cert_get(self, data_dic: Dict[str, str]) -> str:\n        \"\"\"get certificate\"\"\"\n        self.logger.debug(\"CAhandler._cert_get()\")\n\n        url = f\"{self.api_host}/issue_certificate\"\n        code, api_response = self._api_post(url, data_dic)\n\n        if code == 200 and api_response:\n            cert = api_response\n        else:\n            self.logger.error(\"Enrollment failed: %s/%s\", code, api_response)\n            cert = None\n\n        self.logger.debug(\"CAhandler._cert_get() ended\")\n        return cert\n\n    def _cert_status_get(self, certificate: str) -> str:\n        \"\"\"get certificate status\"\"\"\n        self.logger.debug(\"CAhandler._cert_status_get()\")\n\n        data_dic = {\"certificateFile\": certificate}\n        url = f\"{self.api_host}/verify_certificate?issuerName={encode_url(self.logger, self.ca_name)}\"\n        code, api_response = self._api_post(url, data_dic)\n        api_response[\"code\"] = code\n\n        return api_response\n\n    def _enrollment_dic_create(self, csr: str) -> Dict[str, str]:\n        \"\"\"create enrollment dic\"\"\"\n        self.logger.debug(\"CAhandler._enrollment_dic_create()\")\n\n        # get public key from csr\n        csr_pubkey = csr_pubkey_get(self.logger, csr, encoding=\"base64der\")\n        if csr_pubkey:\n            # get CN from csr\n            csr_cn = self._csr_cn_get(csr)\n\n            # calculate validiaty dates\n            validfrom, validto = self._validity_dates_get()\n\n            # prepare payload for api call\n            data_dic = {\n                \"publicKey\": csr_pubkey,\n                \"profileName\": self.profile_name,\n                \"issuerName\": self.ca_name,\n                \"cn\": csr_cn,\n                \"notBefore\": validfrom,\n                \"notAfter\": validto,\n            }\n\n            # get SANs from csr as base64 encoded byte sequence\n            # sans_base64 = csr_san_byte_get(self.logger, csr)\n            # if sans_base64:\n            #    data_dic['extensions'] = [{'oid': '2.5.29.17', 'value': sans_base64}]  # 'Zm9vLmJhci5sb2NhbA=='\n\n        else:\n            self.logger.error(\"Could not extract the public key from CSR\")\n            data_dic = None\n\n        return data_dic\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"profile_name\")\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"api_password\", \"auth\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if not error:\n            # verify issuer\n            error = self._issuer_verify()\n\n        if not error:\n            # verify profile\n            error = self._profile_verify()\n            if not error:\n\n                # get issuer chain\n                issuer_chain = self._issuer_chain_get()\n\n                data_dic = self._enrollment_dic_create(csr)\n                if data_dic:\n                    cert_raw = self._cert_get(data_dic)\n\n                if cert_raw:\n                    cert = convert_byte_to_string(\n                        cert_der2pem(b64_decode(self.logger, cert_raw))\n                    )\n                    cert_bundle = cert + issuer_chain\n                else:\n                    error = \"Enrollment failed\"\n\n        self.logger.debug(\"Certificate.enroll() ended with: %s\", error)\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        error = handler_config_check(\n            self.logger,\n            self,\n            [\n                \"api_host\",\n                \"api_user\",\n                \"api_password\",\n                \"api_key\",\n                \"ca_name\",\n                \"profile_name\",\n            ],\n        )\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        cert: str,\n        _rev_reason: str = \"unspecified\",\n        _rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = None\n        message = None\n        detail = None\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        cert_ski = cert_ski_get(\n            self.logger, cert\n        )  # get subjectKeyIdentifier from certificate\n\n        url = f\"{self.api_host}/revoke_certificate?issuerName={encode_url(self.logger, self.ca_name)}&certificateId={cert_ski}\"\n        data_dic = {}\n        code, content_dic = self._api_post(url, data_dic)\n        if content_dic:\n            message = \"urn:ietf:params:acme:error:serverInternal\"\n            if \"Message\" in content_dic:\n                detail = content_dic.get(\"Message\")\n            elif \"message\" in content_dic:\n                detail = content_dic.get(\"message\")\n            else:\n                detail = \"Unknown error\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/certifier_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ca handler for Insta Certifier via REST-API class\"\"\"\nfrom __future__ import print_function\nimport textwrap\nimport math\nimport time\nimport json\nimport os\nfrom typing import List, Tuple, Dict\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    b64_decode,\n    b64_encode,\n    cert_pem2der,\n    cert_serial_get,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    error_dic_get,\n    handler_config_check,\n    load_config,\n    parse_url,\n    proxy_check,\n    uts_now,\n    uts_to_date_utc,\n)\n\n\nclass CAhandler(object):\n    \"\"\"CA  handler\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None):\n        self.debug = debug\n        self.logger = logger\n        self.request_timeout = 20\n        self.api_host = None\n        self.api_user = None\n        self.api_password = None\n        self.ca_bundle = True\n        self.ca_name = None\n        self.auth = None\n        self.polling_timeout = 60\n        self.profile_id = None\n        self.proxy = None\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        if not self.api_host:\n            self._config_load()\n            self._auth_set()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _auth_set(self):\n        \"\"\"set basic authentication header\"\"\"\n        self.logger.debug(\"CAhandler._auth_set()\")\n        if self.api_user and self.api_password:\n            self.auth = HTTPBasicAuth(self.api_user, self.api_password)\n        else:\n            self.logger.error(\n                'Auth information incomplete. Either \"api_user\" or \"api_password\" parameter is missing in config file'\n            )\n        self.logger.debug(\"CAhandler._auth_set() ended\")\n\n    def _api_poll(self, request_dic: Dict[str, str]) -> Tuple[str, str, str]:\n        \"\"\"poll request\"\"\"\n        self.logger.debug(\"CAhandler._api_poll()\")\n\n        cert_bundle = None\n        cert_raw = None\n\n        if \"certificate\" in request_dic:\n            # poll identifier for later storage\n            cert_dic = requests.get(\n                request_dic[\"certificate\"],\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n            if \"certificateBase64\" in cert_dic:\n                # this is a valid cert generate the bundle\n                error = None\n                cert_bundle = self._pem_cert_chain_generate(cert_dic)\n                cert_raw = cert_dic[\"certificateBase64\"]\n            else:\n                error = \"certificateBase64 is missing in cert request response\"\n        else:\n            error = \"No certificate structure in request response\"\n\n        self.logger.debug(\"CAhandler._api_poll() ended\")\n        return (error, cert_bundle, cert_raw)\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"\n        generic wrapper for an API post call\n        args:\n            url - API URL\n            data - data to post\n        returns:\n            result of the post command\n        \"\"\"\n        try:\n            api_response = requests.post(\n                url=url,\n                json=data,\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\"API post() request returned an error: %s\", err_)\n            api_response = str(err_)\n\n        return api_response\n\n    def _ca_get(\n        self, filter_key: str = None, filter_value: str = None\n    ) -> Dict[str, str]:\n        \"\"\"get list of CAs\"\"\"\n        self.logger.debug(\"_ca_get(%s:%s)\", filter_key, filter_value)\n        params = {}\n\n        if filter_key:\n            params[\"q\"] = f\"{filter_key}:{filter_value}\"\n\n        if self.api_host:\n            try:\n                api_response = requests.get(\n                    self.api_host + \"/v1/cas\",\n                    auth=self.auth,\n                    params=params,\n                    proxies=self.proxy,\n                    verify=self.ca_bundle,\n                    timeout=self.request_timeout,\n                ).json()\n            except Exception as err_:\n                self.logger.error(\"API get() request returned error: %s\", str(err_))\n                api_response = {\n                    \"status\": 500,\n                    \"message\": str(err_),\n                    \"statusMessage\": \"Internal Server Error\",\n                }\n        else:\n            self.logger.error(\"api_host parameter is misisng in configuration\")\n            api_response = {}\n        self.logger.debug(\"CAhandler._ca_get() ended with: %s\", api_response)\n        return api_response\n\n    def _ca_get_properties(self, filter_key: str, filter_value: str) -> Dict[str, str]:\n        \"\"\"get properties for a single CAs\"\"\"\n        self.logger.debug(\"_ca_get_properties(%s:%s)\", filter_key, filter_value)\n        ca_list = self._ca_get(filter_key, filter_value)\n        ca_dic = {}\n        if \"status\" in ca_list and \"message\" in ca_list:\n            # we got an error from get_ca()\n            ca_dic = ca_list\n        elif \"cas\" in ca_list and ca_list[\"cas\"]:\n            for cas in ca_list[\"cas\"]:\n                if filter_key in cas and cas[filter_key] == filter_value:\n                    ca_dic = cas\n                    break\n        if not ca_dic:\n            ca_dic = {\n                \"status\": 404,\n                \"message\": \"CA not found\",\n                \"statusMessage\": \"Not Found\",\n            }\n        self.logger.debug(\"CAhandler._ca_get_properties() ended with: %s\", ca_dic)\n        return ca_dic\n\n    def _cert_get(self, csr: str) -> Dict[str, str]:\n        \"\"\"get certificate from CA\"\"\"\n        self.logger.debug(\"CAhandler._cert_get(%s)\", csr)\n        ca_dic = self._ca_get_properties(\"name\", self.ca_name)\n        cert_dic = {}\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"auth\", \"api_password\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if \"href\" in ca_dic:\n            data = {\"ca\": ca_dic[\"href\"], \"pkcs10\": csr}\n\n            # set profileid if configured\n            if self.profile_id:\n                data[\"profileId\"] = self.profile_id\n\n            cert_dic = self._api_post(self.api_host + \"/v1/requests\", data)\n\n        if not cert_dic:\n            cert_dic = ca_dic\n\n        self.logger.debug(\"CAhandler._cert_get() ended with: %s\", cert_dic)\n        return cert_dic\n\n    def _cert_get_properties(self, serial: str, ca_link: str) -> Dict[str, str]:\n        \"\"\"get properties for a single cert\"\"\"\n        self.logger.debug(\"_cert_get_properties(%s:%s)\", serial, ca_link)\n\n        params = {\"q\": f\"issuer-id:{ca_link},serial-number:{serial}\"}\n        try:\n            api_response = requests.get(\n                self.api_host + \"/v1/certificates\",\n                auth=self.auth,\n                params=params,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\n                \"Could not get certificate properties. Error: %s\", str(err_)\n            )\n            api_response = {\n                \"status\": 500,\n                \"message\": str(err_),\n                \"statusMessage\": \"Internal Server Error\",\n            }\n        self.logger.debug(\"CAhandler._cert_get_properties() ended\")\n        return api_response\n\n    def _certificate_revoke(\n        self, serial: str, ca_dic: Dict[str, str], rev_reason: str, rev_date: str\n    ) -> Tuple[int, str, str]:\n        self.logger.debug(\"CAhandler._certificate_revoke()\")\n\n        code = None\n        message = None\n        detail = None\n\n        # get error message\n        err_dic = error_dic_get(self.logger)\n\n        # get certificate information via rest by search for ca+ serial\n        cert_dic = self._cert_get_properties(serial, ca_dic[\"href\"])\n        if \"certificates\" in cert_dic:\n            if (\n                len(cert_dic[\"certificates\"]) > 0\n                and \"href\" in cert_dic[\"certificates\"][0]\n            ):\n                # revoke the cert\n                data = {\n                    \"newStatus\": \"revoked\",\n                    \"crlReason\": rev_reason,\n                    \"invalidityDate\": rev_date,\n                }\n                cert_dic = self._api_post(\n                    cert_dic[\"certificates\"][0][\"href\"] + \"/status\", data\n                )\n                if \"status\" in cert_dic:\n                    code = 400\n                    message = err_dic[\"alreadyrevoked\"]\n                    if \"message\" in cert_dic:\n                        detail = cert_dic[\"message\"]\n                    else:\n                        detail = \"no details\"\n                else:\n                    code = 200\n                    message = None\n                    detail = None\n            else:\n                code = 404\n                message = err_dic[\"serverinternal\"]\n                detail = \"Cert path could not be found\"\n        else:\n            code = 404\n            message = err_dic[\"serverinternal\"]\n            detail = \"Cert could not be found\"\n\n        return (code, message, detail)\n\n    def _config_user_load(self, config_dic: Dict[str, str]):\n        \"\"\"load username\"\"\"\n        self.logger.debug(\"_config_user_load()\")\n        if (\n            \"api_user\" in config_dic[\"CAhandler\"]\n            or \"api_user_variable\" in config_dic[\"CAhandler\"]\n        ):\n            if \"api_user_variable\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.api_user = os.environ[\n                        config_dic.get(\"CAhandler\", \"api_user_variable\")\n                    ]\n                except Exception as err:\n                    self.logger.error(\"Could not load user_variable:%s\", err)\n            if \"api_user\" in config_dic[\"CAhandler\"]:\n                if self.api_user:\n                    self.logger.info(\"Overwrite api_user\")\n                self.api_user = config_dic.get(\n                    \"CAhandler\", \"api_user\", fallback=self.api_user\n                )\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"api_user\" parameter is missing in config file'\n            )\n\n        self.logger.debug(\"_config_user_load() ended\")\n\n    def _config_password_load(self, config_dic: Dict[str, str]):\n        \"\"\"load password\"\"\"\n        self.logger.debug(\"_config_password_load()\")\n\n        if (\n            \"api_password\" in config_dic[\"CAhandler\"]\n            or \"api_password_variable\" in config_dic[\"CAhandler\"]\n        ):\n            if \"api_password_variable\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.api_password = os.environ[\n                        config_dic.get(\"CAhandler\", \"api_password_variable\")\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load passphrase_variable:%s\",\n                        err,\n                    )\n            if \"api_password\" in config_dic[\"CAhandler\"]:\n                if self.api_password:\n                    self.logger.info(\"Overwrite api_password_variable\")\n                self.api_password = config_dic.get(\"CAhandler\", \"api_password\")\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"api_password\" parameter is missing in config file'\n            )\n\n        self.logger.debug(\"_config_password_load() ended\")\n\n    def _config_parameter_load(self, config_dic: Dict[str, str]):\n        \"\"\"load parameters\"\"\"\n        self.logger.debug(\"_config_parameter_load()\")\n\n        if \"ca_name\" in config_dic[\"CAhandler\"]:\n            self.ca_name = config_dic.get(\"CAhandler\", \"ca_name\", fallback=self.ca_name)\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"ca_name\" parameter is missing in config file'\n            )\n\n        try:\n            self.polling_timeout = int(\n                config_dic.get(\n                    \"CAhandler\", \"polling_timeout\", fallback=self.polling_timeout\n                )\n            )\n        except Exception:\n            self.logger.warning(\n                \"Invalid value for polling_timeout in configuration. Using default: %s\",\n                self.polling_timeout,\n            )\n\n        try:\n            self.request_timeout = int(\n                config_dic.get(\n                    \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                )\n            )\n        except Exception:\n            self.logger.warning(\n                \"Invalid value for request_timeout in configuration. Using default: %s\",\n                self.request_timeout,\n            )\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        # load profile_id\n        self.profile_id = config_dic.get(\"CAhandler\", \"profile_id\", fallback=None)\n\n        # check if we get a ca bundle for verification\n        if \"ca_bundle\" in config_dic[\"CAhandler\"]:\n            try:\n                self.ca_bundle = config_dic.getboolean(\"CAhandler\", \"ca_bundle\")\n            except Exception:\n                self.ca_bundle = config_dic.get(\n                    \"CAhandler\", \"ca_bundle\", fallback=self.ca_bundle\n                )\n\n        self.logger.debug(\"_config_parameter_load() ended\")\n\n    def _config_proxy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load parameters\"\"\"\n        self.logger.debug(\"_config_proxy_load()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                proxy_list = json.loads(config_dic[\"DEFAULT\"][\"proxy_server_list\"])\n                url_dic = parse_url(self.logger, self.api_host)\n                if \"host\" in url_dic:\n                    (fqdn, _port) = url_dic[\"host\"].split(\":\")\n                    proxy_server = proxy_check(self.logger, fqdn, proxy_list)\n                    self.proxy = {\"http\": proxy_server, \"https\": proxy_server}\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse proxy_server_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"_config_proxy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=R0912, R0915\n        self.logger.debug(\"_config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n        if \"CAhandler\" in config_dic:\n            if \"api_host\" in config_dic[\"CAhandler\"]:\n                self.api_host = config_dic.get(\n                    \"CAhandler\", \"api_host\", fallback=self.api_host\n                )\n            else:\n                self.logger.error(\n                    'Configuration incomplete: \"api_host\" parameter is missing in config file'\n                )\n\n            # load user from config\n            self._config_user_load(config_dic)\n            # load password from config\n            self._config_password_load(config_dic)\n            # load parameters from config\n            self._config_parameter_load(config_dic)\n            # load profiling\n            self.eab_profiling, self.eab_handler = config_eab_profile_load(\n                self.logger, config_dic\n            )\n\n            # load profiles\n            self.profiles = config_profile_load(self.logger, config_dic)\n\n            # load header info\n            self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n\n        # load proxy configuration\n        self._config_proxy_load(config_dic)\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _csr_check(self, csr: str) -> str:\n        \"\"\"check csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"profile_id\")\n\n        self.logger.debug(\"CAhandler._csr_check() ended with: %s\", error)\n        return error\n\n    def _poll_cert_get(\n        self, request_dic: Dict[str, str], poll_identifier: str, error: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"get certificate via poll request\"\"\"\n        self.logger.debug(\"CAhandler._poll_cert_get()\")\n\n        cert_bundle = None\n        cert_raw = None\n        break_loop = False\n        # check response\n        if \"status\" in request_dic:\n            if request_dic[\"status\"] == \"accepted\":\n\n                if \"certificate\" in request_dic:\n                    # poll identifier for later storage\n                    cert_dic = requests.get(\n                        request_dic[\"certificate\"],\n                        auth=self.auth,\n                        verify=self.ca_bundle,\n                        proxies=self.proxy,\n                        timeout=self.request_timeout,\n                    ).json()\n                    # pylint: disable=R1723\n                    if \"certificateBase64\" in cert_dic:\n                        # this is a valid cert generate the bundle\n                        error = None\n                        cert_bundle = self._pem_cert_chain_generate(cert_dic)\n                        cert_raw = cert_dic[\"certificateBase64\"]\n                        poll_identifier = None\n                        break_loop = True\n                    else:\n                        error = \"Request accepted but no certificateBase64 returned\"\n                else:\n                    error = \"Request accepted but no certificate returned\"\n            elif request_dic[\"status\"] == \"rejected\":\n                error = \"Request rejected by operator\"\n                poll_identifier = None\n                break_loop = True\n\n        self.logger.debug(\"CAhandler._poll_cert_get() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, break_loop)\n\n    def _loop_poll(self, request_url: str) -> Tuple[str, str, str, str]:\n        \"\"\"poll request\"\"\"\n        self.logger.debug(\"CAhandler._loop_poll(%s)\", request_url)\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        if request_url:\n            # calculate iterations based on timeout\n            poll_cnt = math.ceil(self.polling_timeout / 5)\n            cnt = 1\n            while cnt <= poll_cnt:\n                cnt += 1\n                request_dic = requests.get(\n                    request_url,\n                    auth=self.auth,\n                    verify=self.ca_bundle,\n                    proxies=self.proxy,\n                    timeout=self.request_timeout,\n                ).json()\n\n                # check response\n                (\n                    error,\n                    cert_bundle,\n                    cert_raw,\n                    poll_identifier,\n                    break_loop,\n                ) = self._poll_cert_get(request_dic, request_url, error)\n                if break_loop:\n                    break\n\n                # sleep\n                time.sleep(self.request_timeout)\n        else:\n            self.logger.warning(\"Error during polling loop: no request url specified\")\n            poll_identifier = request_url\n\n        self.logger.debug(\"CAhandler._loop_poll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw, poll_identifier)\n\n    def _pem_list_cert_get(self, cert_dic: Dict[str, str]) -> Dict[str, str]:\n        self.logger.debug(\"CAhandler._pem_list_cert_get()\")\n        if \"issuer\" in cert_dic:\n            self.logger.debug(\"issuer found: %s\", cert_dic[\"issuer\"])\n            ca_cert_dic = requests.get(\n                cert_dic[\"issuer\"],\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        else:\n            self.logger.debug(\"issuer found: %s\", cert_dic[\"issuerCa\"])\n            ca_cert_dic = requests.get(\n                cert_dic[\"issuerCa\"],\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n\n        cert_dic = {}\n        if \"certificates\" in ca_cert_dic:\n            if \"active\" in ca_cert_dic[\"certificates\"]:\n                cert_dic = requests.get(\n                    ca_cert_dic[\"certificates\"][\"active\"],\n                    auth=self.auth,\n                    verify=self.ca_bundle,\n                    proxies=self.proxy,\n                    timeout=self.request_timeout,\n                ).json()\n\n        self.logger.debug(\"CAhandler._pem_list_cert_get() ended\")\n        return cert_dic\n\n    def _pem_list_build(self, cert_dic: Dict[str, str]) -> List[str]:\n        self.logger.debug(\"CAhandler._pem_list_build()\")\n\n        pem_list = []\n        issuer_loop = True\n        while issuer_loop:\n            if \"certificateBase64\" in cert_dic:\n                pem_list.append(cert_dic[\"certificateBase64\"])\n            else:\n                # stop if there is no pem content in the json response\n                issuer_loop = False  # lgtm [py/unused-local-variable]\n                break\n            if \"issuer\" in cert_dic or \"issuerCa\" in cert_dic:\n                cert_dic = self._pem_list_cert_get(cert_dic)\n            else:\n                issuer_loop = False  # lgtm [py/unused-local-variable]\n                break\n\n        self.logger.debug(\"CAhandler._pem_list_build() ended\")\n        return pem_list\n\n    def _pem_cert_chain_generate(self, cert_dic: str) -> str:\n        \"\"\"build certificate chain based\"\"\"\n        self.logger.debug(\"CAhandler._pem_cert_chain_generate()\")\n\n        if cert_dic:\n            pem_list = self._pem_list_build(cert_dic)\n        else:\n            pem_list = []\n\n        if pem_list:\n            pem_file = \"\"\n            for cert in pem_list:\n                pem_file = f\"{pem_file}-----BEGIN CERTIFICATE-----\\n{textwrap.fill(cert, 64)}\\n-----END CERTIFICATE-----\\n\"\n        else:\n            pem_file = None\n\n        self.logger.debug(\"CAhandler._pem_cert_chain_generate() ended\")\n        return pem_file\n\n    def _request_poll(self, request_url: str) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll request\"\"\"\n        self.logger.debug(\"CAhandler._request_poll(%s)\", request_url)\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        poll_identifier = request_url\n        rejected = False\n\n        try:\n            request_dic = requests.get(\n                request_url,\n                auth=self.auth,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err:\n            self.logger.error(\"Polling request returned an error: %s\", err)\n            request_dic = {}\n\n        # check response\n        if \"status\" in request_dic:\n            if request_dic[\"status\"] == \"accepted\":\n                (error, cert_bundle, cert_raw) = self._api_poll(request_dic)\n            elif request_dic[\"status\"] == \"rejected\":\n                error = \"Request rejected by operator\"\n                rejected = True\n            else:\n                error = f'Unknown request status: {request_dic[\"status\"]}'\n        else:\n            error = '\"status\" field not found in response.'\n\n        self.logger.debug(\"CAhandler._request_poll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def _trigger_bundle_build(\n        self, cert_raw: str, ca_dic: Dict[str, str]\n    ) -> Tuple[str, str]:\n        self.logger.debug(\"CAhandler._trigger_bundle_build()\")\n        error = None\n        cert_bundle = None\n\n        # get serial from pem file\n        serial = cert_serial_get(self.logger, cert_raw)\n        if serial:\n            # get certificate information via rest by search for ca+ serial\n            cert_list = self._cert_get_properties(serial, ca_dic[\"href\"])\n            # the first entry is the cert we are looking for\n            if \"certificates\" in cert_list and len(cert_list[\"certificates\"][0]) > 0:\n                cert_dic = cert_list[\"certificates\"][0]\n                cert_bundle = self._pem_cert_chain_generate(cert_dic)\n            else:\n                error = \"no certifcates found in rest query\"\n        else:\n            error = \"serial number lookup via rest failed\"\n\n        self.logger.debug(\"CAhandler._trigger_bundle_build() ended with:  %s\", error)\n        return (error, cert_bundle)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"Certificate.enroll()\")\n        cert_bundle = None\n        cert_raw = None\n        poll_identifier = None\n\n        # check CSR\n        error = self._csr_check(csr)\n\n        # enrollment starts here\n        if not error:\n            cert_dic = self._cert_get(csr)\n        else:\n            cert_dic = None\n\n        if cert_dic:\n            if \"status\" in cert_dic:\n                # this is an error\n                if \"message\" in cert_dic:\n                    error = cert_dic[\"message\"]\n                else:\n                    error = \"unknown error\"\n            elif \"certificateBase64\" in cert_dic:\n                # this is a valid cert generate the bundle\n                cert_bundle = self._pem_cert_chain_generate(cert_dic)\n                cert_raw = cert_dic[\"certificateBase64\"]\n            elif \"href\" in cert_dic:\n                # request is pending\n                (error, cert_bundle, cert_raw, poll_identifier) = self._loop_poll(\n                    cert_dic[\"href\"]\n                )\n            else:\n                error = \"no certificate information found\"\n        else:\n            if not error:\n                error = \"internal error\"\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        error = handler_config_check(\n            self.logger, self, [\"api_host\", \"api_user\", \"api_password\", \"ca_name\"]\n        )\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, str, bool]:\n        \"\"\"poll pending status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        if poll_identifier:\n            (\n                error,\n                cert_bundle,\n                cert_raw,\n                poll_identifier,\n                rejected,\n            ) = self._request_poll(poll_identifier)\n        else:\n            self.logger.debug(\n                \"skipping cert: %s as there is no poll_identifier\", cert_name\n            )\n\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        cert: str,\n        rev_reason: str = \"unspecified\",\n        rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke(%s: %s)\", rev_reason, rev_date)\n\n        # get error message\n        err_dic = error_dic_get(self.logger)\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        # lookup REST-PATH of issuing CA\n        ca_dic = self._ca_get_properties(\"name\", self.ca_name)\n        if \"href\" in ca_dic:\n            # get serial from pem file\n            serial = cert_serial_get(self.logger, cert)\n            if serial:\n                (code, message, detail) = self._certificate_revoke(\n                    serial, ca_dic, rev_reason, rev_date\n                )\n            else:\n                code = 404\n                message = err_dic[\"serverinternal\"]\n                detail = \"failed to get serial number from cert\"\n        else:\n            code = 404\n            message = err_dic[\"serverinternal\"]\n            detail = \"CA could not be found\"\n\n        return (code, message, detail)\n\n    def trigger(self, payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        if payload:\n            # decode payload\n            cert = b64_decode(self.logger, payload)\n            try:\n                # cert is a base64 encoded pem object\n                cert_raw = b64_encode(self.logger, cert_pem2der(cert))\n            except Exception:\n                # cert is a binary der encoded object\n                cert_raw = b64_encode(self.logger, cert)\n\n            # lookup REST-PATH of issuing CA\n            ca_dic = self._ca_get_properties(\"name\", self.ca_name)\n            if \"href\" in ca_dic:\n                (error, cert_bundle) = self._trigger_bundle_build(cert_raw, ca_dic)\n            else:\n                error = \"Cannot find CA\"\n        else:\n            error = \"No payload given\"\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/certsrv.py",
    "content": "\"\"\"\nA Python client for the Microsoft AD Certificate Services web page.\n\nhttps://github.com/magnuswatn/certsrv\n\"\"\"\n\n# pylint: disable=C0209, C0415, R1720, R1705\nimport os\nimport re\nimport base64\nimport logging\nimport warnings\nimport requests\n\n__version__ = \"2.1.1\"\n\nlogger = logging.getLogger(__name__)\n\nTIMEOUT = 30\nUNKOWN_ERR_MSG = \"An unknown error occured\"\nDEPRECATIONWARNING = (\n    \"This function is deprecated. Use the method on the Certsrv class instead\"\n)\n\n\nclass RequestDeniedException(Exception):\n    \"\"\"Signifies that the request was denied by the ADCS server.\"\"\"\n\n    def __init__(self, message, response):\n        Exception.__init__(self, message)\n        self.response = response\n\n\nclass CouldNotRetrieveCertificateException(Exception):\n    \"\"\"Signifies that the certificate could not be retrieved.\"\"\"\n\n    def __init__(self, message, response):\n        Exception.__init__(self, message)\n        self.response = response\n\n\nclass CertificatePendingException(Exception):\n    \"\"\"Signifies that the request needs to be approved by a CA admin.\"\"\"\n\n    def __init__(self, req_id):\n        Exception.__init__(\n            self,\n            \"Your certificate request has been received. \"\n            \"However, you must wait for an administrator to issue the \"\n            \"certificate you requested. Your Request Id is {0}.\".format(req_id),\n        )\n        self.req_id = req_id\n\n\nclass Certsrv(object):\n    \"\"\"\n    Represents a Microsoft AD Certificate Services web server.\n\n    Args:\n        server: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        username: The username for authentication.\n        password: The password for authentication.\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (SSL client certificate) or 'gssapi' (GSSAPI, Kerberos)\n        cafile: A PEM file containing the CA certificates that should be trusted.\n        verify: Boolean to enable/disable CA certificate checking.\n        timeout: The timeout to use against the CA server, in seconds.\n            The default is 30.\n        proxies: Dictionary of proxy server for post of get operations\n            {'http': 'http://foo.bar:3128', 'https': 'socks5://foo.bar:1080'}\n            The default is None\n    Note:\n        If you use a client certificate for authentication (auth_method=cert),\n        the username parameter should be the path to a certificate, and\n        the password parameter the path to a (unencrypted) private key.\n    \"\"\"\n\n    # pylint: disable=r0913\n    def __init__(\n        self,\n        server,\n        url,\n        username,\n        password,\n        auth_method=\"basic\",\n        cafile=None,\n        verify=True,\n        timeout=TIMEOUT,\n        proxies=None,\n    ):\n\n        self.server = server\n        self.url = url\n        self.timeout = timeout\n        self.auth_method = auth_method\n        self.session = requests.Session()\n        self.proxies = proxies\n\n        if not verify:\n            self.session.verify = False\n        elif cafile:\n            self.session.verify = cafile\n        else:\n            # requests uses it's own CA bundle by default\n            # but ADCS servers often have certificates\n            # from private CAs that are locally trusted,\n            # so we try to find, and use, the system bundle\n            # instead. Fallback to requests own.\n            self.session.verify = _get_ca_bundle()\n\n        self._set_credentials(username, password)\n\n        # We need certsrv to think we are a browser,\n        # or otherwise the Content-Type of the retrieved\n        # certificate will be wrong (for some reason).\n        self.session.headers = {\n            \"User-agent\": \"Mozilla/5.0 certsrv (https://github.com/magnuswatn/certsrv)\"\n        }\n\n    def _set_credentials(self, username, password):\n        if self.auth_method == \"ntlm\":\n            from requests_ntlm import HttpNtlmAuth\n\n            self.session.auth = HttpNtlmAuth(username, password)\n        elif self.auth_method == \"cert\":\n            self.session.cert = (username, password)\n        elif self.auth_method == \"gssapi\":\n            from requests_gssapi import HTTPSPNEGOAuth\n            import gssapi\n\n            oid = \"1.3.6.1.5.5.2\"  # SPNEGO\n            # pylint: disable=e1101\n            cred = gssapi.raw.acquire_cred_with_password(\n                gssapi.Name(username, gssapi.NameType.user),\n                password.encode(\"utf-8\"),\n                mechs=[gssapi.OID.from_int_seq(oid)],\n                usage=\"initiate\",\n            )\n            self.session.auth = HTTPSPNEGOAuth(\n                creds=cred.creds, mech=gssapi.OID.from_int_seq(oid)\n            )\n        else:\n            self.session.auth = (username, password)\n\n    def _post(self, url, **kwargs):\n        response = self.session.post(\n            url, timeout=self.timeout, proxies=self.proxies, **kwargs\n        )\n        return self._handle_response(response)\n\n    def _get(self, url, **kwargs):\n        response = self.session.get(\n            url, timeout=self.timeout, proxies=self.proxies, **kwargs\n        )\n        return self._handle_response(response)\n\n    @staticmethod\n    def _handle_response(response):\n\n        logger.debug(\n            \"Sent %s request to %s, with headers:\\n%s\\n\\nand body:\\n%s\",\n            response.request.method,\n            response.request.url,\n            \"\\n\".join(\n                [\"{0}: {1}\".format(k, v) for k, v in response.request.headers.items()]\n            ),\n            response.request.body,\n        )\n\n        try:\n            debug_content = response.content.decode()\n        except UnicodeDecodeError:\n            debug_content = base64.b64encode(response.content)\n\n        logger.debug(\n            \"Recieved response:\\nHTTP %s\\n%s\\n\\n%s\",\n            response.status_code,\n            \"\\n\".join([\"{0}: {1}\".format(k, v) for k, v in response.headers.items()]),\n            debug_content,\n        )\n\n        response.raise_for_status()\n\n        return response\n\n    def get_cert(self, csr, template, encoding=\"b64\", attributes=None):\n        \"\"\"\n        Gets a certificate from the ADCS server.\n\n        Args:\n            csr: The certificate request to submit.\n            template: The certificate template the cert should be issued from.\n            encoding: The desired encoding for the returned certificate.\n                Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n            attributes: Additional Attributes (request attibutes) to be sent along with\n                the request.\n\n        Returns:\n            The issued certificate.\n\n        Raises:\n            RequestDeniedException: If the request was denied by the ADCS server.\n            CertificatePendingException: If the request needs to be approved\n                by a CA admin.\n            CouldNotRetrieveCertificateException: If something went wrong while\n                fetching the cert.\n        \"\"\"\n        cert_attrib = \"CertificateTemplate:{0}\\r\\n\".format(template)\n        if attributes:\n            cert_attrib += attributes\n\n        data = {\n            \"Mode\": \"newreq\",\n            \"CertRequest\": csr,\n            \"CertAttrib\": cert_attrib,\n            \"FriendlyType\": \"Saved-Request Certificate\",\n            \"TargetStoreFlags\": \"0\",\n            \"SaveCert\": \"yes\",\n        }\n\n        url = \"\"\n        if self.url:\n            url = \"{0}/certfnsh.asp\".format(self.url)\n        else:\n            url = \"https://{0}/certsrv/certfnsh.asp\".format(self.server)\n\n        response = self._post(url, data=data)\n\n        # We need to parse the Request ID from the returning HTML page\n        try:\n            req_id = re.search(r\"certnew.cer\\?ReqID=(\\d+)&\", response.text).group(1)\n        except AttributeError:\n            # We didn't find any request ID in the response. It may need approval.\n            if re.search(r\"Certificate Pending\", response.text):\n                req_id = re.search(r\"Your Request Id is (\\d+).\", response.text).group(1)\n                # pylint: disable=w0707\n                raise CertificatePendingException(req_id)\n            else:\n                # Must have failed. Lets find the error message\n                # and raise a RequestDeniedException.\n                try:\n                    error = re.search(\n                        r'The disposition message is \"([^\"]+)', response.text\n                    ).group(1)\n                except AttributeError:\n                    error = UNKOWN_ERR_MSG\n                # pylint: disable=w0707\n                raise RequestDeniedException(error, response.text)\n\n        return self.get_existing_cert(req_id, encoding)\n\n    def get_existing_cert(self, req_id, encoding=\"b64\"):\n        \"\"\"\n        Gets a certificate that has already been created from the ADCS server.\n\n        Args:\n            req_id: The request ID to retrieve.\n            encoding: The desired encoding for the returned certificate.\n                Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n\n        Returns:\n            The issued certificate.\n\n        Raises:\n            CouldNotRetrieveCertificateException: If something went wrong\n                while fetching the cert.\n        \"\"\"\n\n        cert_url = \"\"\n        if self.url:\n            cert_url = \"{0}/certnew.cer\".format(self.url)\n        else:\n            cert_url = \"https://{0}/certsrv/certnew.cer\".format(self.server)\n\n        params = {\"ReqID\": req_id, \"Enc\": encoding}\n\n        response = self._get(cert_url, params=params)\n\n        if response.headers[\"Content-Type\"] != \"application/pkix-cert\":\n            # The response was not a cert. Something must have gone wrong\n            try:\n                error = re.search(\n                    \"Disposition message:[^\\t]+\\t\\t([^\\r\\n]+)\", response.text\n                ).group(1)\n\n            except AttributeError:\n                error = UNKOWN_ERR_MSG\n            raise CouldNotRetrieveCertificateException(error, response.text)\n        else:\n            return response.content\n\n    def get_ca_cert(self, encoding=\"b64\"):\n        \"\"\"\n        Gets the (newest) CA certificate from the ADCS server.\n\n        Args:\n            encoding: The desired encoding for the returned certificate.\n                Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n\n        Returns:\n            The newest CA certificate from the server.\n        \"\"\"\n        url = \"\"\n        if self.url:\n            url = \"{0}/certcarc.asp\".format(self.url)\n        else:\n            url = \"https://{0}/certsrv/certcarc.asp\".format(self.server)\n\n        response = self._get(url)\n\n        # We have to check how many renewals this server has had,\n        # so that we get the newest CA cert.\n        renewals = re.search(r\"var nRenewals=(\\d+);\", response.text).group(1)\n\n        cert_url = \"\"\n        if self.url:\n            cert_url = \"{0}/certnew.cer\".format(self.url)\n        else:\n            cert_url = \"https://{0}/certsrv/certnew.cer\".format(self.server)\n        params = {\"ReqID\": \"CACert\", \"Enc\": encoding, \"Renewal\": renewals}\n\n        response = self._get(cert_url, params=params)\n\n        if response.headers[\"Content-Type\"] != \"application/pkix-cert\":\n            raise CouldNotRetrieveCertificateException(UNKOWN_ERR_MSG, response.content)\n\n        return response.content\n\n    def get_chain(self, encoding=\"bin\"):\n        \"\"\"\n        Gets the CA chain from the ADCS server.\n\n        Args:\n            encoding: The desired encoding for the returned certificates.\n                Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n\n        Returns:\n            The CA chain from the server, in PKCS#7 format.\n        \"\"\"\n        url = \"\"\n        if self.url:\n            url = \"{0}/certcarc.asp\".format(self.url)\n        else:\n            url = \"https://{0}/certsrv/certcarc.asp\".format(self.server)\n\n        response = self._get(url)\n\n        # We have to check how many renewals this server has had, so that we get the newest chain\n        renewals = re.search(r\"var nRenewals=(\\d+);\", response.text).group(1)\n\n        chain_url = \"\"\n        if self.url:\n            chain_url = \"{0}/certnew.p7b\".format(self.url)\n        else:\n            chain_url = \"https://{0}/certsrv/certnew.p7b\".format(self.server)\n\n        params = {\"ReqID\": \"CACert\", \"Renewal\": renewals, \"Enc\": encoding}\n\n        chain_response = self._get(chain_url, params=params)\n\n        if chain_response.headers[\"Content-Type\"] != \"application/x-pkcs7-certificates\":\n            raise CouldNotRetrieveCertificateException(\n                UNKOWN_ERR_MSG, chain_response.content\n            )\n\n        return chain_response.content\n\n    def check_credentials(self):\n        \"\"\"\n        Checks the specified credentials against the ADCS server.\n\n        Returns:\n            True if authentication succeeded, False if it failed.\n        \"\"\"\n        url = \"\"\n        if self.url:\n            url = \"{0}\".format(self.url)\n        else:\n            url = \"https://{0}/certsrv/\".format(self.server)\n\n        try:\n            self._get(url)\n        except requests.exceptions.HTTPError as error:\n            if error.response.status_code == 401:\n                return False\n            else:\n                raise\n        return True\n\n    def update_credentials(self, username, password):\n        \"\"\"\n        Updates the credentials used against the ADCS server.\n\n        Args:\n            username: The username for authentication.\n            password: The password for authentication.\n        \"\"\"\n        if self.auth_method in (\"ntlm\", \"cert\", \"gssapi\"):\n            # NTLM and SSL is connection based,\n            # so we need to close the connection\n            # to be able to re-authenticate\n            self.session.close()\n        self._set_credentials(username, password)\n\n\ndef _get_ca_bundle():\n    \"\"\"Tries to find the platform ca bundle for the system (on linux systems)\"\"\"\n    ca_bundles = [\n        # list taken from https://golang.org/src/crypto/x509/root_linux.go\n        \"/etc/ssl/certs/ca-certificates.crt\",  # Debian/Ubuntu/Gentoo etc.\n        \"/etc/pki/tls/certs/ca-bundle.crt\",  # Fedora/RHEL 6\n        \"/etc/ssl/ca-bundle.pem\",  # OpenSUSE\n        \"/etc/pki/tls/cacert.pem\",  # OpenELEC\n        \"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem\",  # CentOS/RHEL 7\n    ]\n    for ca_bundle in ca_bundles:\n        if os.path.isfile(ca_bundle):\n            return ca_bundle\n    # if the bundle was not found, we revert back to requests own\n    return True\n\n\n# pylint: disable=r0913\ndef get_cert(server, csr, template, username, password, encoding=\"b64\", **kwargs):\n    \"\"\"\n    Gets a certificate from a Microsoft AD Certificate Services web page.\n\n    Args:\n        server: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        csr: The certificate request to submit.\n        template: The certificate template the cert should be issued from.\n        username: The username for authentication.\n        pasword: The password for authentication.\n        encoding: The desired encoding for the returned certificate.\n            Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (ssl client certificate) or 'gssapi' (GSSAPI, Kerberos).\n        cafile: A PEM file containing the CA certificates that should be trusted.\n\n    Returns:\n        The issued certificate.\n\n    Raises:\n        RequestDeniedException: If the request was denied by the ADCS server.\n        CertificatePendingException: If the request needs to be approved by a CA admin.\n        CouldNotRetrieveCertificateException: If something went wrong while\n            fetching the cert.\n\n    Note:\n        This method is deprecated.\n\n    \"\"\"\n    warnings.warn(\n        DEPRECATIONWARNING,\n        DeprecationWarning,\n    )\n    certsrv = Certsrv(server, username, password, **kwargs)\n    return certsrv.get_cert(csr, template, encoding)\n\n\ndef get_existing_cert(server, req_id, username, password, encoding=\"b64\", **kwargs):\n    \"\"\"\n    Gets a certificate that has already been created from a\n    Microsoft AD Certificate Services web page.\n\n    Args:\n        server: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        req_id: The request ID to retrieve.\n        username: The username for authentication.\n        pasword: The password for authentication.\n        encoding: The desired encoding for the returned certificate.\n            Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (ssl client certificate) or 'gssapi' (GSSAPI, Kerberos).\n        cafile: A PEM file containing the CA certificates that should be trusted.\n\n    Returns:\n        The issued certificate.\n\n    Raises:\n        CouldNotRetrieveCertificateException: If something went wrong while\n            fetching the cert.\n\n    Note:\n        This method is deprecated.\n    \"\"\"\n    warnings.warn(\n        DEPRECATIONWARNING,\n        DeprecationWarning,\n    )\n    certsrv = Certsrv(server, username, password, **kwargs)\n    return certsrv.get_existing_cert(req_id, encoding)\n\n\ndef get_ca_cert(server, username, password, encoding=\"b64\", **kwargs):\n    \"\"\"\n    Gets the (newest) CA certificate from a Microsoft AD Certificate Services web page.\n\n    Args:\n        server: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        username: The username for authentication.\n        pasword: The password for authentication.\n        encoding: The desired encoding for the returned certificate.\n            Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (ssl client certificate) or 'gssapi' (GSSAPI, Kerberos).\n        cafile: A PEM file containing the CA certificates that should be trusted.\n\n    Returns:\n        The newest CA certificate from the server.\n\n    Note:\n        This method is deprecated.\n    \"\"\"\n    warnings.warn(\n        DEPRECATIONWARNING,\n        DeprecationWarning,\n    )\n    certsrv = Certsrv(server, username, password, **kwargs)\n    return certsrv.get_ca_cert(encoding)\n\n\ndef get_chain(server, username, password, encoding=\"bin\", **kwargs):\n    \"\"\"\n    Gets the chain from a Microsoft AD Certificate Services web page.\n\n    Args:\n        server: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        username: The username for authentication.\n        pasword: The password for authentication.\n        encoding: The desired encoding for the returned certificates.\n            Possible values are 'bin' for binary and 'b64' for Base64 (PEM).\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (ssl client certificate) or 'gssapi' (GSSAPI, Kerberos).\n        cafile: A PEM file containing the CA certificates that should be trusted.\n\n    Returns:\n        The CA chain from the server, in PKCS#7 format.\n\n    Note:\n        This method is deprecated.\n    \"\"\"\n    warnings.warn(\n        DEPRECATIONWARNING,\n        DeprecationWarning,\n    )\n    certsrv = Certsrv(server, username, password, **kwargs)\n    return certsrv.get_chain(encoding)\n\n\ndef check_credentials(server, username, password, **kwargs):\n    \"\"\"\n    Checks the specified credentials against the specified ADCS server.\n\n    Args:\n        ca: The FQDN to a server running the Certification Authority\n            Web Enrollment role (must be listening on https).\n        username: The username for authentication.\n        pasword: The password for authentication.\n        auth_method: The chosen authentication method. Either 'basic' (the default),\n            'ntlm', 'cert' (ssl client certificate) or 'gssapi' (GSSAPI, Kerberos).\n        cafile: A PEM file containing the CA certificates that should be trusted.\n\n    Returns:\n        True if authentication succeeded, False if it failed.\n\n    Note:\n        This method is deprecated.\n    \"\"\"\n    warnings.warn(\n        DEPRECATIONWARNING,\n        DeprecationWarning,\n    )\n    certsrv = Certsrv(server, username, password, **kwargs)\n    return certsrv.check_credentials()\n"
  },
  {
    "path": "examples/ca_handler/cmp_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ca handler for generic cmpv2 ca handler\"\"\"\nfrom __future__ import print_function\nimport os\nimport shutil\nimport subprocess\nimport tempfile\nfrom typing import List, Tuple, Dict\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    load_config,\n    build_pem_file,\n    b64_url_recode,\n    config_profile_load,\n)\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.config_dic = {}\n        self.openssl_bin = None\n        self.tmp_dir = None\n        self.recipient = None\n        self.ref = None\n        self.secret = None\n        self.ca_pubs_file = None\n        self.cert_file = None\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.openssl_bin:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _certs_bundle(self) -> Tuple[str, str]:\n        \"\"\"create needed cert(bundle)\"\"\"\n        self.logger.debug(\"CAhandler._certs_bundle()\")\n\n        cert_raw = None\n        cert_bundle = None\n        ca_pem = None\n\n        if os.path.isfile(self.ca_pubs_file):\n            with open(self.ca_pubs_file, \"r\", encoding=\"utf-8\") as fso:\n                ca_pem = fso.read()\n\n        # open certificate\n        if os.path.isfile(self.cert_file):\n            with open(self.cert_file, \"r\", encoding=\"utf-8\") as fso:\n                cert_raw = fso.read()\n\n            # create bundle and raw cert\n            if cert_raw and ca_pem:\n                cert_bundle = cert_raw + ca_pem\n            elif cert_raw:\n                cert_bundle = cert_raw\n            if cert_raw:\n                cert_raw = cert_raw.replace(\"-----BEGIN CERTIFICATE-----\\n\", \"\")\n                cert_raw = cert_raw.replace(\"-----END CERTIFICATE-----\\n\", \"\")\n                cert_raw = cert_raw.replace(\"\\n\", \"\")\n        self.logger.debug(\n            \"CAhandler._certs_bundle() ended with %s/%s\",\n            bool(cert_bundle),\n            bool(cert_raw),\n        )\n        return (cert_bundle, cert_raw)\n\n    def _config_refsecret_load(self, config_dic: Dict[str, str]):\n        \"\"\" \" load ref secrets from file\"\"\"\n        self.logger.debug(\"CAhandler._config_refsecret_load()\")\n\n        if \"CAhandler\" in config_dic and \"cmp_ref\" in config_dic[\"CAhandler\"]:\n            if self.ref:\n                self.logger.info(\"Overwrite cmp_ref variable\")\n            self.ref = config_dic[\"CAhandler\"][\"cmp_ref\"]\n        if \"CAhandler\" in config_dic and \"cmp_secret\" in config_dic[\"CAhandler\"]:\n            if self.secret:\n                self.logger.info(\"Overwrite cmp_secret variable\")\n            self.secret = config_dic[\"CAhandler\"][\"cmp_secret\"]\n\n        self.logger.debug(\"CAhandler._config_refsecret_load() ended\")\n\n    def _config_paramters_load(self):\n        \"\"\" \" load refsecrets from file\"\"\"\n        self.logger.debug(\"CAhandler._config_paramters_load()\")\n\n        if \"cmd\" not in self.config_dic:\n            self.config_dic[\"cmd\"] = \"ir\"\n        if \"popo\" not in self.config_dic:\n            self.config_dic[\"popo\"] = 0\n\n        # create temporary directory\n        self.tmp_dir = tempfile.mkdtemp()\n        self.ca_pubs_file = f\"{self.tmp_dir}/capubs.pem\"\n        self.cert_file = f\"{self.tmp_dir}/cert.pem\"\n\n        # defaulting openssl_bin\n        if not self.openssl_bin:\n            self.logger.warning(\n                \"cmp_openssl_bin parameter missing in configuration. Using default: /usr/bin/openssl\"\n            )\n            self.openssl_bin = \"/usr/bin/openssl\"\n\n        if not self.recipient:\n            self.logger.error(\"cmp_recipient parameter missing in configuration.\")\n\n        self.logger.debug(\"CAhandler._config_paramters_load() ended\")\n\n    def _config_cmprecipient_load(self, config_dic: Dict[str, str]):\n        \"\"\"load and format recipient\"\"\"\n        self.logger.debug(\"CAhandler._config_cmprecipient_load()\")\n\n        if config_dic[\"CAhandler\"][\"cmp_recipient\"].startswith(\"/\"):\n            value = config_dic[\"CAhandler\"][\"cmp_recipient\"]\n        else:\n            value = \"/\" + config_dic[\"CAhandler\"][\"cmp_recipient\"]\n        value = value.replace(\", \", \"/\")\n        value = value.replace(\",\", \"/\")\n        self.config_dic[\"recipient\"] = value\n\n        self.logger.debug(\"CAhandler._config_cmprecipient_load() ended\")\n\n    def _config_cmpparameter_load(self, ele: str, config_dic: Dict[str, str]):\n        \"\"\"load cmp parameters\"\"\"\n        self.logger.debug(\"CAhandler._config_cmpparameter_load()\")\n\n        if ele == \"cmp_openssl_bin\":\n            self.openssl_bin = config_dic[\"CAhandler\"][\"cmp_openssl_bin\"]\n        elif ele == \"cmp_recipient\":\n            self._config_cmprecipient_load(config_dic)\n        elif ele == \"cmp_ref_variable\":\n            try:\n                self.ref = os.environ[config_dic[\"CAhandler\"][\"cmp_ref_variable\"]]\n            except Exception as err:\n                self.logger.error(\"Could not load cmp_ref:%s\", err)\n        elif ele == \"cmp_secret_variable\":\n            try:\n                self.secret = os.environ[config_dic[\"CAhandler\"][\"cmp_secret_variable\"]]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load cmp_secret_variable:%s\",\n                    err,\n                )\n        elif ele in (\"cmp_secret\", \"cmp_ref\"):\n            self.logger.debug(\"CAhandler._config_cmpparameter_load() ignore %s\", ele)\n        else:\n            if (\n                config_dic[\"CAhandler\"][ele] == \"True\"\n                or config_dic[\"CAhandler\"][ele] == \"False\"\n            ):\n                self.config_dic[ele[4:]] = config_dic.getboolean(\n                    \"CAhandler\", ele, fallback=False\n                )\n            else:\n                self.config_dic[ele[4:]] = config_dic[\"CAhandler\"][ele]\n\n        self.logger.debug(\"CAhandler._config_cmpparameter_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=R0912, R0915\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n            for ele in config_dic[\"CAhandler\"]:\n                if ele.startswith(\"cmp_\"):\n                    self._config_cmpparameter_load(ele, config_dic)\n\n        # load ref/psk information\n        self._config_refsecret_load(config_dic)\n        # load file and directory names\n        self._config_paramters_load()\n\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _opensslcmd_build(self) -> List[str]:\n        \"\"\"build openssl command\"\"\"\n        self.logger.debug(\"CAhandler._opensslcmd_build()\")\n\n        cmd_list = [self.openssl_bin, \"cmp\"]\n        for ele, value in self.config_dic.items():\n            cmd_list.append(f\"-{str(ele)}\")\n            if value is not True:\n                cmd_list.append(str(value))\n\n        cmd_list.extend(\n            [\n                \"-csr\",\n                f\"{self.tmp_dir}/csr.pem\",\n                \"-extracertsout\",\n                self.ca_pubs_file,\n                \"-certout\",\n                self.cert_file,\n            ]\n        )\n\n        # set timeouts if not configured\n        if \"-msg_timeout\" not in cmd_list:\n            cmd_list.extend([\"-msg_timeout\", \"5\"])\n        if \"-total_timeout\" not in cmd_list:\n            cmd_list.extend([\"-total_timeout\", \"10\"])\n\n        if self.secret and self.ref:\n            cmd_list.extend([\"-ref\", self.ref])\n        if self.secret and self.ref:\n            cmd_list.extend([\"-secret\", self.secret])\n\n        self.logger.debug(\n            \"CAhandler._opensslcmd_build() ended with: %s\", \" \".join(cmd_list)\n        )\n        return cmd_list\n\n    def _file_save(self, filename: str, content: str):\n        \"\"\"save content to file\"\"\"\n        self.logger.debug(\"CAhandler._file_save(%s)\", filename)\n        with open(filename, \"w\", encoding=\"utf-8\") as fso:\n            fso.write(content)\n        self.logger.debug(\"CAhandler._file_save() ended\")\n\n    def _tmp_dir_delete(self):\n        \"\"\"delete temp files\"\"\"\n        self.logger.debug(\"CAhandler._tmp_dir_delete(%s)\", self.tmp_dir)\n\n        if os.path.exists(self.tmp_dir):\n            shutil.rmtree(self.tmp_dir)\n        else:\n            self.logger.error(\"Could not delate tmp_dir: %s\", self.tmp_dir)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, bool]:\n        \"\"\"enroll certificate from via MS certsrv\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n        cert_bundle = None\n        error = None\n        cert_raw = None\n\n        if self.openssl_bin:\n\n            # prepare the CSR to be signed\n            csr = build_pem_file(\n                self.logger, None, b64_url_recode(self.logger, csr), None, True\n            )\n            # dump csr key\n            self._file_save(f\"{self.tmp_dir}/csr.pem\", csr)\n\n            # build openssl command and run it\n            openssl_cmd = self._opensslcmd_build()\n            rcode = subprocess.call(openssl_cmd)\n            if rcode:\n                self.logger.error(f\"Enrollment failed with rcode: {rcode}\")\n                error = \"rc from enrollment not 0\"\n\n            # generate certificates we need to return\n            if os.path.isfile(f\"{self.tmp_dir}/cert.pem\"):\n                (cert_bundle, cert_raw) = self._certs_bundle()\n            else:\n                error = \"Enrollment failed\"\n\n            # delete temporary files\n            self._tmp_dir_delete()\n\n        else:\n            error = \"Config incomplete\"\n\n        self.logger.debug(\"Certificate.enroll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw, None)\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, _cert: str, _rev_reason: str, _rev_date: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.tsg_id_lookup()\")\n\n        # get serial from pem file and convert to formated hex\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[int, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/digicert_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"CA handler using Digicert CertCentralAPI\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple, Dict\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    b64_encode,\n    cert_pem2der,\n    cert_serial_get,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    csr_cn_lookup,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    handler_config_check,\n    load_config,\n    request_operation,\n    uts_now,\n    uts_to_date_utc,\n)\n\n\nCONTENT_TYPE = \"application/json\"\n\n\nclass CAhandler(object):\n    \"\"\"Digicert CertCentralAP handler\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.api_url = \"https://www.digicert.com/services/v2/\"\n        self.api_key = None\n        self.cert_type = \"ssl_basic\"\n        self.signature_hash = \"sha256\"\n        self.order_validity = 1\n        self.proxy = None\n        self.request_timeout = 10\n        self.organization_id = None\n        self.organization_name = None\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.api_key:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _api_get(self, url: str) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_get()\")\n        headers = {\"X-DC-DEVKEY\": self.api_key, \"Content-Type\": CONTENT_TYPE}\n        code, content = request_operation(\n            self.logger,\n            method=\"get\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=None,\n        )\n\n        self.logger.debug(\"CAhandler._api_get() ended with code: %s\", code)\n        return code, content\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_post()\")\n        headers = {\"X-DC-DEVKEY\": self.api_key, \"Content-Type\": CONTENT_TYPE}\n        code, content = request_operation(\n            self.logger,\n            method=\"post\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n        )\n\n        self.logger.debug(\"CAhandler._api_post() ended with code: %s\", code)\n        return code, content\n\n    def _api_put(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_put()\")\n        headers = {\"X-DC-DEVKEY\": self.api_key, \"Content-Type\": CONTENT_TYPE}\n        code, content = request_operation(\n            self.logger,\n            method=\"put\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n        )\n\n        self.logger.debug(\"CAhandler._api_put() ended with code: %s\", code)\n        return code, content\n\n    def _config_check(self) -> str:\n        \"\"\"check config\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n\n        error = handler_config_check(\n            self.logger, self, [\"api_url\", \"api_key\", \"organization_name\"]\n        )\n\n        self.logger.debug(\"CAhandler._config_check() ended with: %s\", error)\n        return error\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n        if \"CAhandler\" in config_dic:\n            self.api_url = config_dic.get(\n                \"CAhandler\", \"api_url\", fallback=\"https://www.digicert.com/services/v2/\"\n            )\n            self.api_key = config_dic.get(\"CAhandler\", \"api_key\", fallback=self.api_key)\n            self.cert_type = config_dic.get(\n                \"CAhandler\", \"cert_type\", fallback=\"ssl_basic\"\n            )\n            self.signature_hash = config_dic.get(\n                \"CAhandler\", \"signature_hash\", fallback=\"sha256\"\n            )\n            try:\n                self.order_validity = int(\n                    config_dic.get(\"CAhandler\", \"order_validity\", fallback=1)\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load order_validity:%s\",\n                    err,\n                )\n\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\"CAhandler\", \"request_timeout\", fallback=10)\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load request_timeout:%s\",\n                    err,\n                )\n                self.request_timeout = 10\n            self.organization_id = config_dic.get(\n                \"CAhandler\", \"organization_id\", fallback=self.organization_id\n            )\n            self.organization_name = config_dic.get(\n                \"CAhandler\", \"organization_name\", fallback=self.organization_name\n            )\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _order_send(self, csr: str, csr_cn) -> Tuple[str, str]:\n        \"\"\"place certificate order\"\"\"\n        self.logger.debug(\"CAhandler._order_send()\")\n        order_url = f\"{self.api_url}order/certificate/{self.cert_type}\"\n\n        if self.enrollment_config_log:\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if not csr.endswith(\"=\"):\n            # padding if needed\n            csr = csr + \"=\" * (-len(csr) % 4)\n\n        if (\n            (not self.organization_id or self.eab_profiling)\n            and self.organization_name\n            and self.api_key\n        ):\n            self.organization_id = self._organiation_id_get()\n\n        if self.organization_id:\n            data_dic = {\n                \"certificate\": {\n                    \"common_name\": csr_cn,\n                    \"csr\": csr,\n                    \"signature_hash\": self.signature_hash,\n                    \"server_platform\": {\"id\": 34},\n                },\n                \"organization\": {\n                    \"id\": self.organization_id,\n                },\n                \"order_validity\": {\"years\": self.order_validity},\n            }\n            # enroll certificate\n            code, content = self._api_post(order_url, data_dic)\n        else:\n            self.logger.error(\"Configuration incomplete: organisation_id is missing\")\n            code = 500\n            content = \"organisation_id is missing\"\n\n        self.logger.debug(\"CAhandler._order_send() ended with code: %s\", code)\n        return code, content\n\n    def _order_response_parse(self, content: Dict[str, str]) -> Tuple[str, str, str]:\n        \"\"\"parse order response\"\"\"\n        self.logger.debug(\"CAhandler._order_response_parse()\")\n\n        cert_bundle = None\n        cert_raw = None\n        poll_identifier = None\n\n        if content and \"certificate_chain\" in content:\n            cert_bundle = \"\"\n            for cert in content[\"certificate_chain\"]:\n                if \"pem\" in cert:\n                    cert_bundle += cert[\"pem\"] + \"\\n\"\n                else:\n                    self.logger.error(\n                        \"Order response parsing failed: no pem in certificate_chain\"\n                    )\n            cert_raw = b64_encode(\n                self.logger, cert_pem2der(content[\"certificate_chain\"][0][\"pem\"])\n            )\n            if \"id\" in content:\n                poll_identifier = content[\"id\"]\n            else:\n                self.logger.error(\n                    \"Polling_identifier generation failed: no id in response\"\n                )\n        else:\n            self.logger.error(\n                \"Order response parsing failed: no certificate_chain in response\"\n            )\n\n        self.logger.debug(\"CAhandler._order_response_parse() ended\")\n        return cert_bundle, cert_raw, poll_identifier\n\n    def _organiation_id_get(self):\n        \"\"\"get organization ID\"\"\"\n        self.logger.debug(\"CAhandler._organiation_id_get()\")\n\n        org_url = f\"{self.api_url}organization\"\n        code, content = self._api_get(org_url)\n\n        organization_id = None\n        if code in (200, 201):\n            for org in content[\"organizations\"]:\n                if org[\"name\"] == self.organization_name:\n                    self.logger.debug(\n                        \"CAhandler._organiation_id_get() found organization ID: %s\",\n                        org[\"id\"],\n                    )\n                    organization_id = org[\"id\"]\n                    break\n\n        if not organization_id:\n            self.logger.error(\"Could not get organization id.\")\n\n        self.logger.debug(\n            \"CAhandler._organiation_id_get() ended with: %s\", organization_id\n        )\n        return organization_id\n\n    def _csr_check(self, csr: str) -> str:\n        \"\"\"check csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"cert_type\")\n\n        self.logger.debug(\"CAhandler._csr_check() ended with: %s\", error)\n        return error\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        # check configuration\n        error = self._config_check()\n\n        if not error:\n            # check csr and profiling\n            error = self._csr_check(csr)\n\n            if not error:\n                csr_cn = csr_cn_lookup(self.logger, csr)\n                code, content = self._order_send(csr, csr_cn)\n\n                if code in (200, 201):\n                    # successful\n                    (\n                        cert_bundle,\n                        cert_raw,\n                        poll_indentifier,\n                    ) = self._order_response_parse(content)\n                else:\n                    if \"errors\" in content:\n                        error = (\n                            f\"Error during order creation: {code} - {content['errors']}\"\n                        )\n                    else:\n                        error = f\"Error during order creation: {code} - {content}\"\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = self._config_check()\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        certificate_raw: str,\n        _rev_reason: str = \"unspecified\",\n        _rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = None\n        message = None\n        detail = None\n\n        cert_serial = cert_serial_get(self.logger, certificate_raw, hexformat=True)\n\n        if cert_serial:\n            # modify handler configuration in case of eab profiling\n            if self.eab_profiling:\n                self._config_check()\n\n            revocation_url = f\"{self.api_url}certificate/{cert_serial}/revoke\"\n            data_dic = {\"skip_approval\": True}\n            code, detail = self._api_put(revocation_url, data_dic)\n            if code == 204:\n                # rewrite reponse code to not confuse with success\n                code = 200\n        else:\n            code = 500\n            detail = \"Failed to parse certificate serial\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/ejbca_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ejbca rest ca handler\"\"\"\nimport os\nfrom typing import Tuple, Dict\nimport requests\nfrom requests_pkcs12 import Pkcs12Adapter\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    b64_decode,\n    b64_url_recode,\n    build_pem_file,\n    cert_der2pem,\n    cert_issuer_get,\n    cert_serial_get,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    convert_byte_to_string,\n    csr_cn_get,\n    csr_san_get,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    encode_url,\n    enrollment_config_log,\n    handler_config_check,\n    load_config,\n)\n\n\nclass CAhandler(object):\n    \"\"\"ejbca rest handler class\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.api_host = None\n        self.ca_bundle = True\n        self.ca_name = None\n        self.cert_passphrase = None\n        self.cert_profile_name = None\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.ee_profile_name = None\n        self.enrollment_code = None\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.header_info_field = False\n        self.logger = logger\n        self.profiles = {}\n        self.proxy = None\n        self.request_timeout = 5\n        self.session = None\n        self.username = None\n        self.username_append_cn = False\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.api_host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _api_put(self, url: str) -> Dict[str, str]:\n        \"\"\"generic wrapper for an API put call\"\"\"\n        self.logger.debug(\"_api_put(%s)\", url)\n\n        try:\n            api_response = self.session.put(\n                url,\n                proxies=self.proxy,\n                verify=self.ca_bundle,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\"API put() returned error: %s\", err_)\n            api_response = str(err_)\n\n        return api_response\n\n    def _cert_status_check(self, issuer_dn: str, cert_serial: str) -> Dict[str, str]:\n        \"\"\"check certificate status\"\"\"\n        self.logger.debug(\n            \"CAhandler._cert_status_check(%s: %s)\", issuer_dn, cert_serial\n        )\n\n        # define path\n        path = f\"/ejbca/ejbca-rest-api/v1/certificate/{encode_url(self.logger, issuer_dn)}/{cert_serial}/revocationstatus\"\n\n        if self.api_host:\n            try:\n                certstatus_response = self.session.get(\n                    self.api_host + path,\n                    proxies=self.proxy,\n                    verify=self.ca_bundle,\n                    timeout=self.request_timeout,\n                ).json()\n            except Exception as err_:\n                self.logger.error(\n                    \"Certificate status check returned error: %s\", str(err_)\n                )\n                certstatus_response = {\"status\": \"nok\", \"error\": str(err_)}\n        else:\n            self.logger.error(\"api_host parameter is missing in configuration\")\n            certstatus_response = {}\n\n        return certstatus_response\n\n    def _config_server_load(self, config_dic: Dict[str, str]):\n        \"\"\"load server information\"\"\"\n        self.logger.debug(\"CAhandler._config_auth_load()\")\n\n        if \"CAhandler\" in config_dic:\n\n            self.api_host = config_dic.get(\"CAhandler\", \"api_host\", fallback=None)\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\"CAhandler\", \"request_timeout\", fallback=5)\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load request_timeout parameter:%s\",\n                    err,\n                )\n                self.request_timeout = 5\n\n            self.ca_bundle = config_dic.get(\"CAhandler\", \"ca_bundle\", fallback=True)\n            if self.ca_bundle == \"False\":\n                self.ca_bundle = False\n\n        self.logger.debug(\"CAhandler._config_server_load() ended\")\n\n    def _config_authuser_load(self, config_dic: Dict[str, str]):\n        self.logger.debug(\"CAhandler._config_authuser_load()\")\n        if (\n            \"username_variable\" in config_dic[\"CAhandler\"]\n            or \"username\" in config_dic[\"CAhandler\"]\n        ):\n            if \"username_variable\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.username = os.environ[\n                        config_dic.get(\"CAhandler\", \"username_variable\", fallback=None)\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load username_variable:%s\",\n                        err,\n                    )\n\n            if \"username\" in config_dic[\"CAhandler\"]:\n                if self.username:\n                    self.logger.info(\"Overwrite username parameter\")\n                self.username = config_dic.get(\"CAhandler\", \"username\", fallback=None)\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"username\" parameter is missing in config file'\n            )\n\n        # check if we need to add the common name of a certificate to the username\n        try:\n            self.username_append_cn = config_dic.getboolean(\n                \"CAhandler\", \"username_append_cn\", fallback=False\n            )\n        except Exception:\n            self.logger.error(\n                \"Could not load username_append_cn parameter, using default value: False\"\n            )\n            self.username_append_cn = False\n\n        self.logger.debug(\"CAhandler._config_auth_load() ended\")\n\n    def _config_enrollmentcode_load(self, config_dic: Dict[str, str]):\n        self.logger.debug(\"CAhandler._config_enrollmentcode_load()\")\n        if (\n            \"enrollment_code_variable\" in config_dic[\"CAhandler\"]\n            or \"enrollment_code\" in config_dic[\"CAhandler\"]\n        ):\n            if \"enrollment_code_variable\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.enrollment_code = os.environ[\n                        config_dic.get(\"CAhandler\", \"enrollment_code_variable\")\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load enrollment_code_variable:%s\",\n                        err,\n                    )\n\n            if \"enrollment_code\" in config_dic[\"CAhandler\"]:\n                if self.enrollment_code:\n                    self.logger.info(\"Overwrite enrollment_code\")\n                self.enrollment_code = config_dic.get(\"CAhandler\", \"enrollment_code\")\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"enrollment_code\" parameter is missing in config file'\n            )\n\n        self.logger.debug(\"CAhandler._config_enrollmentcode_load() ended\")\n\n    def _config_session_load(self, config_dic: Dict[str, str]):\n        self.logger.debug(\"CAhandler._config_session_load()\")\n\n        if (\n            \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]\n            or \"cert_passphrase\" in config_dic[\"CAhandler\"]\n        ):\n            if \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.cert_passphrase = os.environ[\n                        config_dic.get(\n                            \"CAhandler\", \"cert_passphrase_variable\", fallback=None\n                        )\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load cert_passphrase_variable:%s\",\n                        err,\n                    )\n\n            if \"cert_passphrase\" in config_dic[\"CAhandler\"]:\n                if self.cert_passphrase:\n                    self.logger.info(\n                        \"CAhandler._config_load() overwrite cert_passphrase\"\n                    )\n                self.cert_passphrase = config_dic.get(\"CAhandler\", \"cert_passphrase\")\n\n        if (\n            config_dic\n            and \"cert_file\" in config_dic[\"CAhandler\"]\n            and self.cert_passphrase\n        ):\n            with requests.Session() as self.session:\n                self.session.mount(\n                    self.api_host,\n                    Pkcs12Adapter(\n                        pkcs12_filename=config_dic[\"CAhandler\"][\"cert_file\"],\n                        pkcs12_password=self.cert_passphrase,\n                    ),\n                )\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.'\n            )\n\n        self.logger.debug(\"CAhandler._config_session_load() ended\")\n\n    def _config_auth_load(self, config_dic: Dict[str, str]):\n        \"\"\"load authentication information\"\"\"\n        self.logger.debug(\"CAhandler._config_authuser_load()\")\n\n        if \"CAhandler\" in config_dic:\n            # load user\n            self._config_authuser_load(config_dic)\n            self._config_enrollmentcode_load(config_dic)\n            self._config_session_load(config_dic)\n\n        self.logger.debug(\"CAhandler._config_auth_load() ended\")\n\n    def _config_cainfo_load(self, config_dic: Dict[str, str]):\n        \"\"\"load ca information\"\"\"\n        self.logger.debug(\"CAhandler._config_cainfo_load()\")\n\n        if \"CAhandler\" in config_dic:\n            self.ca_name = config_dic.get(\"CAhandler\", \"ca_name\", fallback=self.ca_name)\n            self.cert_profile_name = config_dic.get(\n                \"CAhandler\", \"cert_profile_name\", fallback=self.cert_profile_name\n            )\n            self.ee_profile_name = config_dic.get(\n                \"CAhandler\", \"ee_profile_name\", fallback=self.ee_profile_name\n            )\n\n        self.logger.debug(\"CAhandler._config_cainfo_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        # load configuration\n        self._config_server_load(config_dic)\n        self._config_auth_load(config_dic)\n        self._config_cainfo_load(config_dic)\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n\n        # check configuration for completeness\n        variable_dic = self.__dict__\n        for ele in [\n            \"api_host\",\n            \"cert_profile_name\",\n            \"ee_profile_name\",\n            \"ca_name\",\n            \"username\",\n            \"enrollment_code\",\n        ]:\n            if not variable_dic[ele]:\n                self.logger.error(\n                    'Configuration incomplete: parameter \"%s\" is missing in configuration file.',\n                    ele,\n                )\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"generic wrapper for an API post call\"\"\"\n        self.logger.debug(\"_api_post(%s)\", url)\n\n        try:\n            api_response = self.session.post(\n                url,\n                json=data,\n                proxies=self.proxy,\n                verify=self.ca_bundle,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\"API post() returned error: %s\", err_)\n            api_response = str(err_)\n\n        return api_response\n\n    def _csr_cn_get(self, csr: str) -> str:\n        \"\"\"get CN from csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_cn_get()\")\n\n        cn = csr_cn_get(self.logger, csr)\n\n        if not cn:\n            self.logger.info(\"CN not found in CSR\")\n            san_list = csr_san_get(self.logger, csr)\n            if san_list:\n                (_type, san_value) = san_list[0].split(\":\")\n                cn = san_value\n                self.logger.info(\n                    \"CN not found in CSR. Using first SAN entry as CN: %s\",\n                    san_value,\n                )\n            else:\n                self.logger.error(\"CN not found in CSR. No SAN entries found\")\n\n        self.logger.debug(\"CAhandler._csr_cn_get() ended with: %s\", cn)\n        return cn\n\n    def _enroll(self, csr: str) -> Tuple[str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n        cert_bundle = None\n        error = None\n        cert_raw = None\n\n        if self.enrollment_config_log:\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        sign_response = self._sign(csr)\n\n        if \"certificate\" in sign_response and \"certificate_chain\" in sign_response:\n            cert_raw = sign_response[\"certificate\"]\n            cert_bundle = convert_byte_to_string(\n                cert_der2pem(b64_decode(self.logger, cert_raw))\n            )\n            for ca_cert in sign_response[\"certificate_chain\"]:\n                cert_bundle = f\"{cert_bundle}{convert_byte_to_string(cert_der2pem(b64_decode(self.logger, ca_cert)))}\"\n        else:\n            error = \"Malformed response\"\n            self.logger.error(\n                \"Enrollment error. Malformed Rest response: %s\", sign_response\n            )\n\n        self.logger.debug(\"CAhandler._enroll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n\n    def _status_get(self) -> Dict[str, str]:\n        \"\"\"get status of the rest-api\"\"\"\n        self.logger.debug(\"_status_get()\")\n\n        if self.api_host:\n            try:\n                api_response = self.session.get(\n                    self.api_host + \"/ejbca/ejbca-rest-api/v1/certificate/status\",\n                    proxies=self.proxy,\n                    verify=self.ca_bundle,\n                    timeout=self.request_timeout,\n                ).json()\n            except Exception as err_:\n                self.logger.error(\n                    \"Could not get certificate status. Error: %s\", str(err_)\n                )\n                api_response = {\"status\": \"nok\", \"error\": str(err_)}\n        else:\n            self.logger.error(\n                \"Configuration incomplete: api_host parameter is missing in configuration\"\n            )\n            api_response = {}\n\n        self.logger.debug(\"CAhandler._status_get() ended\")\n        return api_response\n\n    def _sign(self, csr: str) -> Dict[str, str]:\n        \"\"\"submit CSR for signing\"\"\"\n        self.logger.debug(\"CAhandler._sign()\")\n\n        if self.username_append_cn:\n            username = f\"{self.username}{self._csr_cn_get(csr)}\"\n        else:\n            username = self.username\n        self.logger.debug(\"CAhandler._sign() username: %s\", username)\n\n        # prepare the CSR to be signed\n        csr = build_pem_file(\n            self.logger, None, b64_url_recode(self.logger, csr), None, True\n        )\n\n        data_dic = {\n            \"certificate_request\": csr,\n            \"certificate_profile_name\": self.cert_profile_name,\n            \"end_entity_profile_name\": self.ee_profile_name,\n            \"certificate_authority_name\": self.ca_name,\n            \"username\": username,\n            \"password\": self.enrollment_code,\n            \"include_chain\": True,\n        }\n\n        if self.api_host:\n            sign_response = self._api_post(\n                self.api_host + \"/ejbca/ejbca-rest-api/v1/certificate/pkcs10enroll\",\n                data_dic,\n            )\n        else:\n            self.logger.error(\n                \"Configuration incomplete: api_host is missing in configuration\"\n            )\n            sign_response = {}\n\n        return sign_response\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"process csr\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        status_dic = self._status_get()\n\n        if \"status\" in status_dic and status_dic[\"status\"].lower() == \"ok\":\n\n            # check for eab profiling and header_info\n            error = eab_profile_header_info_check(\n                self.logger, self, csr, \"cert_profile_name\"\n            )\n\n            if not error:\n                # cnroll certificate\n                (error, cert_bundle, cert_raw) = self._enroll(csr)\n            else:\n                self.logger.error(\n                    \"Enrollment error. CSR got rejected with error: %s\", error\n                )\n        else:\n            # error in status respoinse from ejbca rest api\n            if \"error\" in status_dic:\n                error = status_dic[\"error\"]\n            else:\n                error = \"Unknown error\"\n                self.logger.error(\"Enrollment failed: Unknown error\")\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = handler_config_check(\n            self.logger,\n            self,\n            [\n                \"api_host\",\n                \"cert_profile_name\",\n                \"ee_profile_name\",\n                \"ca_name\",\n                \"username\",\n                \"enrollment_code\",\n            ],\n        )\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, cert: str, rev_reason: str = \"UNSPECIFIED\", rev_date: str = None\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke(%s: %s)\", rev_reason, rev_date)\n        code = None\n        message = None\n        detail = None\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        # get cert serial number and issuerdn\n        cert_serial = cert_serial_get(self.logger, cert, hexformat=True)\n        issuer_dn = cert_issuer_get(self.logger, cert)\n\n        # check status\n        certstatus_dic = self._cert_status_check(issuer_dn, cert_serial)\n\n        if \"revoked\" in certstatus_dic:\n            if not certstatus_dic[\"revoked\"]:\n                # this is the revocation path\n                path = f\"/ejbca/ejbca-rest-api/v1/certificate/{encode_url(self.logger, issuer_dn)}/{cert_serial}/revoke?reason={rev_reason.upper()}\"\n                revoke_response = self._api_put(self.api_host + path)\n\n                if \"revoked\" in revoke_response and revoke_response[\"revoked\"]:\n                    code = 200\n                else:\n                    code = 400\n                    message = \"urn:ietf:params:acme:error:serverInternal\"\n                    detail = str(revoke_response)\n            else:\n                # already revoked\n                code = 400\n                message = \"urn:ietf:params:acme:error:alreadyRevoked\"\n                detail = \"Certificate has already been revoked\"\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:serverInternal\"\n            detail = \"Unknown status\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/entrust_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"CA handler using Entrust ECS Enterprise\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple, Dict, List\nimport datetime\nimport os\nimport requests\nfrom requests_pkcs12 import Pkcs12Adapter\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    allowed_domainlist_check,\n    b64_encode,\n    b64_url_recode,\n    cert_pem2der,\n    cert_serial_get,\n    config_allowed_domainlist_load,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    csr_cn_lookup,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    handler_config_check,\n    header_info_get,\n    load_config,\n    request_operation,\n    uts_now,\n    uts_to_date_utc,\n)\n\n\nCONTENT_TYPE = \"application/json\"\n\n\n# hardcoded Entrust Root Certification Authority - G2\nENTRUST_ROOT_CA = \"\"\"-----BEGIN CERTIFICATE-----\nMIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC\nVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50\ncnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs\nIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz\ndCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy\nNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu\ndHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt\ndGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0\naG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj\nYXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T\nRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN\ncCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW\nwcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1\nU1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0\njaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP\nBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN\nBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/\njTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ\nRkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v\n1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R\nnAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH\nVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==\n-----END CERTIFICATE-----\n\"\"\"\n\n\nclass CAhandler(object):\n    \"\"\"Digicert CertCentralAP handler\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.api_url = \"https://api.entrust.net/enterprise/v2\"\n        self.client_cert = None\n        self.cert_passphrase = None\n        self.username = None\n        self.password = None\n        self.organization_name = None\n        self.certtype = \"STANDARD_SSL\"\n        self.cert_validity_days = 365\n        self.entrust_root_cert = ENTRUST_ROOT_CA\n        self.proxy = None\n        self.session = None\n        self.request_timeout = 10\n\n        self.allowed_domainlist = []\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.session:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _api_get(self, url: str) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_get()\")\n        headers = {\"Content-Type\": CONTENT_TYPE}\n\n        code, content = request_operation(\n            self.logger,\n            session=self.session,\n            method=\"get\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=None,\n        )\n        self.logger.debug(\"CAhandler._api_get() ended with code: %s\", code)\n        return code, content\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_post()\")\n        headers = {\"Content-Type\": CONTENT_TYPE}\n        code, content = request_operation(\n            self.logger,\n            session=self.session,\n            method=\"post\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n        )\n        self.logger.debug(\"CAhandler._api_post() ended with code: %s\", code)\n        return code, content\n\n    def _api_put(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_put()\")\n        headers = {\"Content-Type\": CONTENT_TYPE}\n        code, content = request_operation(\n            self.logger,\n            session=self.session,\n            method=\"put\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n        )\n\n        self.logger.debug(\"CAhandler._api_put() ended with code: %s\", code)\n        return code, content\n\n    def _certificates_get_from_serial(self, cert_serial: str) -> List[str]:\n        \"\"\"get certificates\"\"\"\n        self.logger.debug(\"CAhandler._certificates_get_from_serial()\")\n\n        # for some reason entrust custs leading zeros from serial number\n        if cert_serial.startswith(\"0\"):\n            self.logger.info(\"Remove leading zeros from serial number\")\n            cert_serial = cert_serial.lstrip(\"0\")\n\n        code, content = self._api_get(\n            self.api_url + f\"/certificates?serialNumber={cert_serial}\"\n        )\n\n        if code == 200 and \"certificates\" in content:\n            cert_list = content[\"certificates\"]\n        else:\n            self.logger.error(\n                \"Certificate lookup based on serial number failed for %s with code: %s\",\n                cert_serial,\n                code,\n            )\n            cert_list = []\n\n        self.logger.debug(\n            \"CAhandler._certificates_get_from_serial() ended with code: %s and %s certificate\",\n            code,\n            len(cert_list),\n        )\n        return cert_list\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n        if \"CAhandler\" in config_dic:\n            self.api_url = config_dic.get(\n                \"CAhandler\", \"api_url\", fallback=\"https://api.entrust.net/enterprise/v2\"\n            )\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\n                        \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                    )\n                )\n            except Exception as err:\n                self.logger.error(\"Failed to parse request_timeout parameter: %s\", err)\n            try:\n                self.cert_validity_days = int(\n                    config_dic.get(\n                        \"CAhandler\",\n                        \"cert_validity_days\",\n                        fallback=self.cert_validity_days,\n                    )\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Failed to parse cert_validity_days %s parameter\",\n                    err,\n                )\n\n            self.username = config_dic.get(\n                \"CAhandler\", \"username\", fallback=self.username\n            )\n            self.password = config_dic.get(\n                \"CAhandler\", \"password\", fallback=self.password\n            )\n            self.organization_name = config_dic.get(\n                \"CAhandler\", \"organization_name\", fallback=self.organization_name\n            )\n            self.certtype = config_dic.get(\n                \"CAhandler\", \"certtype\", fallback=\"STANDARD_SSL\"\n            )\n            self._config_session_load(config_dic)\n\n            # load root CA\n            self._config_root_load(config_dic)\n\n        # load allowed domainlist\n        self.allowed_domainlist = config_allowed_domainlist_load(\n            self.logger, config_dic\n        )\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _config_passphrase_load(self, config_dic: Dict[str, str]):\n        \"\"\"load passphrase\"\"\"\n        self.logger.debug(\"CAhandler._config_passphrase_load()\")\n        if (\n            \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]\n            or \"cert_passphrase\" in config_dic[\"CAhandler\"]\n        ):\n            if \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\n                    \"CAhandler._config_passphrase_load(): load passphrase from environment variable\"\n                )\n                try:\n                    self.cert_passphrase = os.environ[\n                        config_dic.get(\n                            \"CAhandler\",\n                            \"cert_passphrase_variable\",\n                            fallback=self.cert_passphrase,\n                        )\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load cert_passphrase_variable:%s\",\n                        err,\n                    )\n\n            if \"cert_passphrase\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\n                    \"CAhandler._config_passphrase_load(): load passphrase from config file\"\n                )\n                if self.cert_passphrase:\n                    self.logger.info(\"Overwrite cert_passphrase\")\n                self.cert_passphrase = config_dic.get(\n                    \"CAhandler\", \"cert_passphrase\", fallback=self.cert_passphrase\n                )\n        self.logger.debug(\"CAhandler._config_passphrase_load() ended\")\n\n    def _config_root_load(self, config_dic: Dict[str, str]):\n        \"\"\"load root CA\"\"\"\n        self.logger.debug(\"CAhandler._config_root_load()\")\n        if \"entrust_root_cert\" in config_dic[\"CAhandler\"]:\n            if os.path.isfile(config_dic[\"CAhandler\"][\"entrust_root_cert\"]):\n                self.logger.debug(\n                    \"CAhandler._config_root_load(): load root CA from config file\"\n                )\n                with open(\n                    config_dic.get(\"CAhandler\", \"entrust_root_cert\"),\n                    \"r\",\n                    encoding=\"utf8\",\n                ) as ca_file:\n                    self.entrust_root_cert = ca_file.read()\n            else:\n                self.logger.error(\n                    \"Root CA file configured but not not found. Using default one.\"\n                )\n\n        self.logger.debug(\"CAhandler._config_root_load() ended\")\n\n    def _config_session_load(self, config_dic: Dict[str, str]):\n        \"\"\"load session\"\"\"\n        self.logger.debug(\"CAhandler._config_session_load()\")\n\n        with requests.Session() as self.session:\n            # client auth via pem files\n            if (\n                \"client_cert\" in config_dic[\"CAhandler\"]\n                and \"client_key\" in config_dic[\"CAhandler\"]\n            ):\n                self.logger.debug(\n                    \"CAhandler._config_session_load() cert and key in pem format\"\n                )\n                self.session.cert = (\n                    config_dic.get(\"CAhandler\", \"client_cert\"),\n                    config_dic.get(\"CAhandler\", \"client_key\"),\n                )\n\n            else:\n                self._config_passphrase_load(config_dic)\n                if \"client_cert\" in config_dic[\"CAhandler\"] and self.cert_passphrase:\n                    self.logger.debug(\n                        \"CAhandler._config_session_load() cert and passphrase\"\n                    )\n                    self.session.mount(\n                        self.api_url,\n                        Pkcs12Adapter(\n                            pkcs12_filename=config_dic.get(\"CAhandler\", \"client_cert\"),\n                            pkcs12_password=self.cert_passphrase,\n                        ),\n                    )\n                else:\n                    self.logger.warning(\n                        'Configuration might be incomplete: \"client_cert\", \"client_key\" or \"client_passphrase[_variable]\" parameter is missing in config file'\n                    )\n            self.session.auth = (self.username, self.password)\n\n        self.logger.debug(\"CAhandler._config_session_load() ended\")\n\n    def _org_domain_cfg_check(self) -> str:\n        \"\"\"check organizations\"\"\"\n        self.logger.debug(\"CAhandler._organizations_check()\")\n\n        error = None\n        org_dic = self._organizations_get()\n        if self.organization_name not in org_dic:\n            error = f\"Organization {self.organization_name} not found in Entrust API\"\n            self.logger.error(\"organizations check ended with error: %s\", error)\n        else:\n            domain_list = self._domains_get(org_dic[self.organization_name])\n            if not self.allowed_domainlist:\n                self.logger.info(\n                    \"Allowed_domainlist is empty, using domains from Entrust API\"\n                )\n                self.allowed_domainlist = domain_list\n\n        self.logger.debug(\"CAhandler._organizations_check() ended with %s\", error)\n        return error\n\n    def _organizations_get(self) -> Dict[str, str]:\n        \"\"\"get organizations\"\"\"\n        self.logger.debug(\"CAhandler._organizations_get()\")\n\n        code, content = self._api_get(self.api_url + \"/organizations\")\n        org_dic = {}\n        if code == 200 and \"organizations\" in content:\n            self.logger.debug(\"CAhandler._organizations_get() ended with code: 200\")\n            for org in content[\"organizations\"]:\n                if (\n                    \"verificationStatus\" in org\n                    and org[\"verificationStatus\"] == \"APPROVED\"\n                    and \"name\" in org\n                    and \"clientId\" in org\n                ):\n                    org_dic[org[\"name\"]] = org[\"clientId\"]\n        else:\n            self.logger.error(\n                \"Malformed response while getting the organization list from API\"\n            )\n\n        self.logger.debug(\"CAhandler._organizations_get() ended with code: %s\", code)\n        return org_dic\n\n    def _domains_get(self, org_id: str) -> List[str]:\n        \"\"\"get domains\"\"\"\n        self.logger.debug(\"CAhandler._domains_get()\")\n\n        code, content = self._api_get(self.api_url + f\"/clients/{org_id}/domains\")\n\n        api_domain_list = []\n        if code == 200 and \"domains\" in content:\n            self.logger.debug(\"CAhandler._domains_get() ended with code: 200\")\n\n            for domain in content[\"domains\"]:\n                if domain.get(\"verificationStatus\", None) == \"APPROVED\" and domain.get(\n                    \"domainName\", None\n                ):\n                    api_domain_list.append(domain.get(\"domainName\"))\n        else:\n            self.logger.error(\n                \"Malformed response while getting the domain list from API\"\n            )\n\n        self.logger.debug(\"CAhandler._domains_get() ended with code: %s\", code)\n        return api_domain_list\n\n    def credential_check(self):\n        \"\"\"test connection to Entrust api\"\"\"\n        self.logger.debug(\"CAhandler.credential_check()\")\n\n        error = None\n        code, content = self._api_get(self.api_url + \"/application/version\")\n        if code != 200:\n            error = f\"Connection to Entrust API failed: {content}\"\n\n        self.logger.debug(\"CAhandler.credential_check() ended with code: %s\", code)\n        return error\n\n    def _config_check(self) -> str:\n        \"\"\"check config\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n\n        error = None\n        for ele in [\"api_url\", \"username\", \"password\", \"organization_name\"]:\n            if not getattr(self, ele):\n                error = f\"{ele} parameter in missing in config file\"\n                self.logger.error(\"Configuration check ended with error: %s\", error)\n                break\n\n        self.logger.debug(\"CAhandler._config_check() ended with: %s\", error)\n        return error\n\n    def _enroll_check(self, csr: str) -> str:\n        \"\"\"check csr\"\"\"\n        self.logger.debug(\"CAhandler._enroll_check()\")\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"cert_type\")\n\n        if not error:\n            error = self._config_check()\n\n        if not error:\n            error = self._org_domain_cfg_check()\n\n        if not error:\n            # check for allowed domainlist\n            error = allowed_domainlist_check(self.logger, csr, self.allowed_domainlist)\n\n        if not error:\n            error = self.credential_check()\n\n        self.logger.debug(\"CAhandler._enroll_check() ended with %s\", error)\n        return error\n\n    def _trackingid_get(self, cert_raw: str) -> int:\n        \"\"\"get tracking id\"\"\"\n        self.logger.debug(\"CAhandler._trackingid_get()\")\n\n        tracking_id = None\n        # we misuse header_info_get() to get the tracking id from database\n        cert_recode = b64_url_recode(self.logger, cert_raw)\n        pid_list = header_info_get(\n            self.logger,\n            csr=cert_recode,\n            vlist=[\"poll_identifier\"],\n            field_name=\"cert_raw\",\n        )\n\n        for ele in pid_list:\n            if \"poll_identifier\" in ele:\n                tracking_id = ele[\"poll_identifier\"]\n                break\n\n        if not tracking_id:\n            # lookup through Entrust API\n            self.logger.info(\n                \"Tracking_id not found in database. Lookup trough Entrust API\"\n            )\n            cert_serial = cert_serial_get(self.logger, cert_raw, hexformat=True)\n            certificate_list = self._certificates_get_from_serial(cert_serial)\n            for ele in certificate_list:\n                if \"trackingId\" in ele:\n                    tracking_id = ele[\"trackingId\"]\n                    break\n\n        self.logger.debug(\"CAhandler._trackingid_get() ended with %s\", tracking_id)\n        return tracking_id\n\n    def _response_parse(self, content: Dict[str, str]) -> Tuple[str, str]:\n        \"\"\"parse response\"\"\"\n        self.logger.debug(\"CAhandler._response_parse()\")\n\n        cert_bundle = None\n        cert_raw = None\n        poll_indentifier = None\n\n        if \"trackingId\" in content:\n            poll_indentifier = content[\"trackingId\"]\n        if \"endEntityCert\" in content and \"chainCerts\" in content:\n            cert_raw = b64_encode(self.logger, cert_pem2der(content[\"endEntityCert\"]))\n            for cnt, ca_cert in enumerate(content[\"chainCerts\"]):\n                if cnt == 0:\n                    cert_bundle = ca_cert + \"\\n\"\n                else:\n                    cert_bundle += ca_cert + \"\\n\"\n\n            # add Entrust Root CA\n            if cert_bundle:\n                cert_bundle += self.entrust_root_cert + \"\\n\"\n            else:\n                cert_bundle = self.entrust_root_cert + \"\\n\"\n        self.logger.debug(\"CAhandler._response_parse() ended\")\n        return cert_bundle, cert_raw, poll_indentifier\n\n    def _enroll(self, csr: str) -> Tuple[str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n\n        error = None\n        cert_raw = None\n        cert_bundle = None\n        poll_indentifier = None\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend(\n                [\"cert_passphrase\", \"client_key\"]\n            )\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        # get CN and SANs\n        cn = csr_cn_lookup(self.logger, csr)\n\n        # calculate cert expiry date\n        certexpirydate = datetime.datetime.now() + datetime.timedelta(\n            days=self.cert_validity_days\n        )\n\n        data_dic = {\n            \"csr\": csr,\n            \"signingAlg\": \"SHA-2\",\n            \"eku\": \"SERVER_AND_CLIENT_AUTH\",\n            \"cn\": cn,\n            \"org\": self.organization_name,\n            \"endUserKeyStorageAgreement\": True,\n            \"certType\": self.certtype,\n            \"certExpiryDate\": certexpirydate.strftime(\"%Y-%m-%d\"),\n        }\n\n        code, content = self._api_post(self.api_url + \"/certificates\", data_dic)\n\n        if code == 201:\n            cert_bundle, cert_raw, poll_indentifier = self._response_parse(content)\n        else:\n            if \"errors\" in content:\n                error = f\"Error during order creation: {code} - {content['errors']}\"\n            else:\n                error = f\"Error during order creation: {code} - {content}\"\n            self.logger.error(\"Enrollment failed with error: %s\", error)\n\n        self.logger.debug(\"CAhandler._enroll() ended with code: %s\", code)\n        return error, cert_bundle, cert_raw, poll_indentifier\n\n    def revoke_by_trackingid(\n        self, tracking_id: int, _rev_reason: str = \"unspecified\"\n    ) -> Tuple[int, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke_by_trackingid()\")\n        code, content = self._api_post(\n            self.api_url + f\"/certificates/{tracking_id}/revocations\",\n            {\n                \"crlReason\": _rev_reason,\n                \"revocationComment\": \"revoked by acme2certifier\",\n            },\n        )\n        self.logger.debug(\"CAhandler.revoke_by_trackingid() ended with code: %s\", code)\n        return code, content\n\n    def _total_get(self, content: str) -> int:\n        \"\"\"get total number of certificates\"\"\"\n        self.logger.debug(\"CAhandler._total_get()\")\n        total = 1\n\n        if (\n            isinstance(content, dict)\n            and \"summary\" in content\n            and \"total\" in content[\"summary\"]\n        ):\n            self.logger.debug(\n                \"CAhandler.certificates_get() total number of certificates: %s\",\n                content[\"summary\"][\"total\"],\n            )\n            total = content[\"summary\"][\"total\"]  # get total number of certificates\n        else:\n            self.logger.error(\n                \"Error while trying to get the certificate totals. Did not get any total value: %s\",\n                content,\n            )\n            raise StopIteration(\n                \"Certificates lookup failed: did not get any total value\"\n            )\n\n        self.logger.debug(\"CAhandler._total_get() ended with %s\", total)\n        return total\n\n    def certificates_get(self, limit=200) -> List[str]:\n        \"\"\"get certificates\"\"\"\n        self.logger.debug(\"CAhandler.certificates_get()\")\n\n        # set initial values\n        offset = 0\n        cert_list = []\n        prev_data = []\n        total = 1\n        while len(cert_list) < total:\n            self.logger.info(\n                \"fetching certs offset: %s, limit: %s, total: %s, buffered: %s\",\n                offset,\n                limit,\n                total,\n                len(cert_list),\n            )\n            code, content = self._api_get(\n                self.api_url + f\"/certificates?limit={limit}&offset={offset}\"\n            )\n            if code == 200:\n                if offset == 0:\n                    # updte totals or throw error\n                    total = self._total_get(content)\n\n                # extend certificate list or throw error\n                if \"certificates\" in content:\n                    # cover cases where we wont get new data as we have to avoid loops\n                    if prev_data != content[\"certificates\"]:\n                        cert_list.extend(content[\"certificates\"])\n                        prev_data = content[\"certificates\"]\n                        offset = offset + limit\n                    else:\n                        self.logger.info(\n                            \"Could not get get new certificate data in loop. Stopping the loop.\"\n                        )\n                        break\n            else:\n                self.logger.error(\"Getting certificate data failed with code: %s\", code)\n                break\n\n        self.logger.debug(\n            \"CAhandler.certificates_get() ended with code: %s and %s certificate\",\n            code,\n            len(cert_list),\n        )\n        return cert_list\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        error = self._enroll_check(csr)\n\n        if not error:\n            error, cert_bundle, cert_raw, poll_indentifier = self._enroll(csr)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return error, cert_bundle, cert_raw, poll_indentifier\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.handler_check()\")\n        error = handler_config_check(\n            self.logger, self, [\"username\", \"password\", \"organization_name\"]\n        )\n        self.logger.debug(\"CAhandler.handler_check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        certificate_raw: str,\n        _rev_reason: str = \"unspecified\",\n        _rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = None\n        message = None\n        detail = None\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, certificate_raw)\n\n        # get tracking id as input for revocation call\n        tracking_id = self._trackingid_get(certificate_raw)\n\n        if tracking_id:\n            code, content = self.revoke_by_trackingid(tracking_id, _rev_reason)\n            if code == 200:\n                message = \"Certificate revoked\"\n            else:\n                code = 500\n                message = \"urn:ietf:params:acme:error:serverInternal\"\n                detail = content\n        else:\n            code = 500\n            message = \"urn:ietf:params:acme:error:serverInternal\"\n            detail = \"Failed to get tracking id\"\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/est_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ca handler for generic EST server\"\"\"\nfrom __future__ import print_function\nimport os\nimport textwrap\nimport json\nfrom typing import List, Tuple, Dict\nimport requests\nfrom requests.auth import HTTPBasicAuth\nfrom requests_pkcs12 import Pkcs12Adapter\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives.serialization.pkcs7 import (\n    load_pem_pkcs7_certificates,\n    load_der_pkcs7_certificates,\n)\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    load_config,\n    b64_decode,\n    b64_url_recode,\n    convert_byte_to_string,\n    convert_string_to_byte,\n    parse_url,\n    proxy_check,\n    handler_config_check,\n)\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.est_host = None\n        self.est_client_cert = False\n        self.cert_passphrase = False\n        self.est_user = None\n        self.est_password = None\n        self.ca_bundle = True\n        self.proxy = None\n        self.request_timeout = 20\n        self.session = None\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.est_host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _cacerts_get(self) -> Tuple[str, str]:\n        \"\"\"get ca certs from cerver\"\"\"\n        self.logger.debug(\"CAhandler._cacerts_get()\")\n        error = None\n        if self.est_host:\n            try:\n                if self.est_client_cert:\n                    self.logger.debug(\"CAhandler._cacerts_get() by using client-certs\")\n                    # client auth\n                    response = self.session.get(\n                        self.est_host + \"/cacerts\",\n                        verify=self.ca_bundle,\n                        proxies=self.proxy,\n                        timeout=self.request_timeout,\n                    )\n                else:\n                    self.logger.debug(\n                        \"CAhandler._cacerts_get() by using userid/password\"\n                    )\n                    response = self.session.get(\n                        self.est_host + \"/cacerts\",\n                        auth=HTTPBasicAuth(self.est_user, self.est_password),\n                        verify=self.ca_bundle,\n                        proxies=self.proxy,\n                        timeout=self.request_timeout,\n                    )\n                pem = self._pkcs7_to_pem(b64_decode(self.logger, response.text))\n            except Exception as err_:\n                self.logger.error(\"Error while getting the CA certificates: %s\", err_)\n                error = err_\n                pem = None\n        else:\n            self.logger.error(\n                'Configuration incomplete: \"est_host\" parameter is missing'\n            )\n            error = None\n            pem = None\n\n        self.logger.debug(\"CAhandler._cacerts_get() ended with err: %s\", error)\n        return (error, pem)\n\n    def _cert_bundle_create(\n        self, error: str, ca_pem: str, cert_raw: str\n    ) -> Tuple[str, str, str]:\n        \"\"\"create cert bundle\"\"\"\n        self.logger.debug(\"CAhandler._cert_bundle_create()\")\n\n        cert_bundle = None\n        if not error:\n            cert_bundle = cert_raw + ca_pem\n            cert_raw = cert_raw.replace(\"-----BEGIN CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"-----END CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"\\n\", \"\")\n        else:\n            self.logger.error(\"Simpleenroll error: %s\", error)\n\n        self.logger.debug(\"CAhandler._cert_bundle_create()\")\n        return (error, cert_bundle, cert_raw)\n\n    def _config_host_load(self, config_dic: Dict[str, str]):\n        \"\"\"load est server address\"\"\"\n        self.logger.debug(\"CAhandler._config_host_load()\")\n\n        if \"est_host_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.est_host = (\n                    os.environ[config_dic.get(\"CAhandler\", \"est_host_variable\")]\n                    + \"/.well-known/est\"\n                )\n            except Exception as err:\n                self.logger.error(\"Could not load est_host_variable:%s\", err)\n        if \"est_host\" in config_dic[\"CAhandler\"]:\n            if self.est_host:\n                self.logger.info(\"Overwrite est_host\")\n            self.est_host = config_dic.get(\"CAhandler\", \"est_host\") + \"/.well-known/est\"\n        if not self.est_host:\n            self.logger.error('Missing \"est_host\" parameter')\n\n        self.logger.debug(\"CAhandler._config_host_load() ended\")\n\n    def _cert_passphrase_load(self, config_dic: Dict[str, str]):\n        \"\"\"load cert passphrase\"\"\"\n        self.logger.debug(\"CAhandler._cert_passphrase_load()\")\n        if \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.cert_passphrase = os.environ[\n                    config_dic.get(\"CAhandler\", \"cert_passphrase_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load cert_passphrase_variable:%s\",\n                    err,\n                )\n        if \"cert_passphrase\" in config_dic[\"CAhandler\"]:\n            if self.cert_passphrase:\n                self.logger.info(\"Overwrite cert_passphrase\")\n            self.cert_passphrase = config_dic.get(\"CAhandler\", \"cert_passphrase\")\n        self.logger.debug(\"CAhandler._cert_passphrase_load() ended\")\n\n    def _config_clientauth_load(self, config_dic: Dict[str, str]):\n        \"\"\"check if we need to use clientauth\"\"\"\n        self.logger.debug(\"CAhandler._config_clientauth_load()\")\n\n        # client auth via pem files\n        if \"est_client_cert\" in config_dic[\"CAhandler\"]:\n            if \"est_client_key\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\"CAhandler._config_clientauth_load(): load pem\")\n                self.est_client_cert = config_dic.get(\n                    \"CAhandler\", \"est_client_cert\", fallback=self.est_client_cert\n                )\n                self.session.cert = (\n                    config_dic.get(\"CAhandler\", \"est_client_cert\"),\n                    config_dic.get(\"CAhandler\", \"est_client_key\"),\n                )\n            elif (\n                \"cert_passphrase\" in config_dic[\"CAhandler\"]\n                or \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]\n            ):\n                self.logger.debug(\"CAhandler._config_clientauth_load(): load pkcs12\")\n                self.est_client_cert = config_dic.get(\"CAhandler\", \"est_client_cert\")\n                self._cert_passphrase_load(config_dic)\n                self.session.mount(\n                    self.est_host,\n                    Pkcs12Adapter(\n                        pkcs12_filename=config_dic.get(\"CAhandler\", \"est_client_cert\"),\n                        pkcs12_password=self.cert_passphrase,\n                    ),\n                )\n            else:\n                self.logger.error(\n                    'Clientauth configuration incomplete: either \"est_client_key or \"cert_passphrase\" parameter is missing in config file'\n                )\n\n        self.logger.debug(\"CAhandler._config_clientauth_load() ended\")\n\n    def _config_userauth_load(self, config_dic: Dict[str, str]):\n        \"\"\"check if we need to use user-auth\"\"\"\n        self.logger.debug(\"CAhandler._config_userauth_load()\")\n\n        if \"est_user_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.est_user = os.environ[\n                    config_dic.get(\"CAhandler\", \"est_user_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\"Could not load est_user_variable:%s\", err)\n        if \"est_user\" in config_dic[\"CAhandler\"]:\n            if self.est_user:\n                self.logger.info(\"CAhandler._config_load() overwrite est_user\")\n            self.est_user = config_dic.get(\"CAhandler\", \"est_user\")\n\n        self.logger.debug(\"CAhandler._config_userauth_load() ended\")\n\n    def _config_password_load(self, config_dic: Dict[str, str]):\n        \"\"\"load password\"\"\"\n        self.logger.debug(\"CAhandler._config_password_load()\")\n\n        if \"est_password_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.est_password = os.environ[\n                    config_dic.get(\"CAhandler\", \"est_password_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\"Could not load est_password:%s\", err)\n        if \"est_password\" in config_dic[\"CAhandler\"]:\n            if self.est_password:\n                self.logger.info(\"Overwrite est_password\")\n            self.est_password = config_dic.get(\"CAhandler\", \"est_password\")\n\n        if (self.est_user and not self.est_password) or (\n            self.est_password and not self.est_user\n        ):\n            self.logger.error(\n                'Configuration incomplete: either \"est_user\" or \"est_password\" parameter is missing in config file'\n            )\n\n        self.logger.debug(\"CAhandler._config_password_load() ended\")\n\n    def _config_parameters_load(self, config_dic: Dict[str, str]):\n        \"\"\"load config paramters\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        # check if we get a ca bundle for verification\n        try:\n            self.ca_bundle = config_dic.getboolean(\"CAhandler\", \"ca_bundle\")\n        except Exception:\n            self.ca_bundle = config_dic.get(\n                \"CAhandler\", \"ca_bundle\", fallback=self.ca_bundle\n            )\n\n        try:\n            self.request_timeout = int(\n                config_dic.get(\n                    \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                )\n            )\n        except Exception:\n            self.logger.error(\n                \"Could not load request_timeout:%s\",\n                config_dic.get(\"CAhandler\", \"request_timeout\"),\n            )\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _config_proxy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load config paramters\"\"\"\n        self.logger.debug(\"CAhandler._config_proxy_load()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                proxy_list = json.loads(config_dic.get(\"DEFAULT\", \"proxy_server_list\"))\n                url_dic = parse_url(self.logger, self.est_host)\n                if \"host\" in url_dic:\n                    (fqdn, _port) = url_dic[\"host\"].split(\":\")\n                    proxy_server = proxy_check(self.logger, fqdn, proxy_list)\n                    self.proxy = {\"http\": proxy_server, \"https\": proxy_server}\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load proxy_server_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"CAhandler._config_proxy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=R0912, R0915\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n\n            with requests.Session() as self.session:\n                # load host information\n                self._config_host_load(config_dic)\n                # load clientauth\n                self._config_clientauth_load(config_dic)\n                # load user\n                self._config_userauth_load(config_dic)\n                # load password\n                self._config_password_load(config_dic)\n                # load paramters\n                self._config_parameters_load(config_dic)\n                # check if we have one authentication scheme\n                if not self.est_client_cert and not self.est_user:\n                    self.logger.error(\n                        \"Configuration incomplete: either user or client authentication must be configured.\"\n                    )\n                elif self.est_client_cert and self.est_user:\n                    self.logger.error(\n                        \"Configuration error: user and client authentication cannot be configured together.\"\n                    )\n                if self.est_client_cert and not self.ca_bundle:\n                    self.logger.error(\n                        \"Configuration error: client authentication requires a ca_bundle.\"\n                    )\n\n        # load proxy information\n        self._config_proxy_load(config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _pkcs7_to_pem(self, pkcs7_content: str, outform: str = \"string\") -> List[str]:\n        \"\"\"convert pkcs7 to pem\"\"\"\n        self.logger.debug(\"CAhandler._pkcs7_to_pem()\")\n\n        try:\n            pkcs7_obj = load_pem_pkcs7_certificates(\n                convert_string_to_byte(pkcs7_content)\n            )\n        except Exception:\n            self.logger.debug(\"CAhandler._pkcs7_to_pem(): load pem failed. Try der...\")\n            pkcs7_obj = load_der_pkcs7_certificates(pkcs7_content)\n\n        cert_pem_list = []\n        for cert in pkcs7_obj:\n            cert_pem_list.append(\n                convert_byte_to_string(cert.public_bytes(serialization.Encoding.PEM))\n            )\n\n        # define output format\n        if outform == \"string\":\n            result = \"\".join(cert_pem_list)\n        elif outform == \"list\":\n            result = cert_pem_list\n        else:\n            result = None\n\n        self.logger.debug(\"Certificate._pkcs7_to_pem() ended\")\n        return result\n\n    def _simpleenroll(self, csr: str) -> Tuple[str, str]:\n        \"\"\"EST /simpleenroll request.\"\"\"\n        self.logger.debug(\"CAhandler._simpleenroll()\")\n        error = None\n        try:\n            headers = {\"Content-Type\": \"application/pkcs10\"}\n            if self.est_client_cert:\n                # client auth\n                response = self.session.post(\n                    self.est_host + \"/simpleenroll\",\n                    data=csr,\n                    headers=headers,\n                    verify=self.ca_bundle,\n                    proxies=self.proxy,\n                    timeout=self.request_timeout,\n                )\n            else:\n                response = self.session.post(\n                    self.est_host + \"/simpleenroll\",\n                    data=csr,\n                    auth=HTTPBasicAuth(self.est_user, self.est_password),\n                    headers=headers,\n                    verify=self.ca_bundle,\n                    proxies=self.proxy,\n                    timeout=self.request_timeout,\n                )\n            # response.raise_for_status()\n            pem = self._pkcs7_to_pem(b64_decode(self.logger, response.text))\n        except Exception as err_:\n            self.logger.error(\"Enrollment error: %s\", err_)\n            error = str(err_)\n            pem = None\n\n        self.logger.debug(\"CAhandler._simpleenroll() ended with err: %s\", error)\n        return (error, pem)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, bool]:\n        \"\"\"enroll certificate from NCLM\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n        cert_bundle = None\n        error = None\n        cert_raw = None\n\n        # recode csr\n        csr = textwrap.fill(b64_url_recode(self.logger, csr), 64) + \"\\n\"\n\n        if self.est_host:\n            (error, ca_pem) = self._cacerts_get()\n\n        if not error:\n            if ca_pem:\n                (error, cert_raw) = self._simpleenroll(csr)\n                # build certificate bundle\n                (error, cert_bundle, cert_raw) = self._cert_bundle_create(\n                    error, ca_pem, cert_raw\n                )\n            else:\n                error = \"no CA certificates found\"\n                self.logger.error(\"No CA certificates found\")\n        else:\n            self.logger.error(\"Error while fetching the CA certificates: %s\", error)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = handler_config_check(self.logger, self, [\"est_host\"])\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, _cert: str, _rev_reason: str, _rev_date: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.tsg_id_lookup()\")\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        self.logger.debug(\"CAhandler.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/ms_wcce/__init__.py",
    "content": "# MIT License\n\n# Copyright (c) 2021 ly4k\n\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\"\"\"Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE) adapted from https://github.com/ly4k/Certipy\"\"\"\n"
  },
  {
    "path": "examples/ca_handler/ms_wcce/errors.py",
    "content": "\"\"\"error.py\"\"\"\n\n# pylint: disable=C0209, R1705\nfrom impacket import hresult_errors\n\n\ndef translate_error_code(error_code: int) -> str:\n    \"\"\"translate error code in something readable\"\"\"\n    error_code &= 0xFFFFFFFF\n    if error_code in hresult_errors.ERROR_MESSAGES:\n        error_msg_short = hresult_errors.ERROR_MESSAGES[error_code][0]\n        error_msg_verbose = hresult_errors.ERROR_MESSAGES[error_code][1]\n        return \"code: 0x%x - %s - %s\" % (\n            error_code,\n            error_msg_short,\n            error_msg_verbose,\n        )\n    else:\n        return \"unknown error code: 0x%x\" % error_code\n"
  },
  {
    "path": "examples/ca_handler/ms_wcce/request.py",
    "content": "\"\"\"request.py\"\"\"\n\n# pylint: disable=C0209, C0415, E0401, R0913, W1201\nimport logging\n\nfrom cryptography import x509\nfrom cryptography.hazmat.primitives.serialization import Encoding\nfrom impacket.dcerpc.v5 import rpcrt\nfrom impacket.dcerpc.v5.dtypes import DWORD, LPWSTR, PBYTE, ULONG\nfrom impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT\nfrom impacket.dcerpc.v5.nrpc import checkNullString\nfrom impacket.uuid import uuidtup_to_bin\n\nfrom examples.ca_handler.ms_wcce.errors import translate_error_code\nfrom examples.ca_handler.ms_wcce.rpc import get_dce_rpc\nfrom examples.ca_handler.ms_wcce.target import Target\n\nNAME = \"req\"\nMSRPC_UUID_ICPR = uuidtup_to_bin((\"91ae6020-9e3c-11cf-8d7c-00aa00c091be\", \"0.0\"))\n\n\ndef csr_pem_to_der(csr: str) -> bytes:\n    \"\"\"convert pem to der\"\"\"\n    csr = x509.load_pem_x509_csr(csr)\n    return csr.public_bytes(Encoding.DER)\n\n\ndef der_to_pem(certificate: bytes) -> bytes:\n    \"\"\"convert der to pem\"\"\"\n    cert = x509.load_der_x509_certificate(certificate)\n    return cert.public_bytes(Encoding.PEM)\n\n\nclass DCERPCSessionError(rpcrt.DCERPCException):\n    \"\"\"error class\"\"\"\n\n    def __init__(self, error_string=None, error_code=None, packet=None):\n        rpcrt.DCERPCException.__init__(self, error_string, error_code, packet)\n\n    def __str__(self) -> str:\n        self.error_code &= 0xFFFFFFFF\n        error_msg = translate_error_code(self.error_code)\n        return \"RequestSessionError: %s\" % error_msg\n\n\n# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/d6bee093-d862-4122-8f2b-7b49102097dc\nclass CERTTRANSBLOB(NDRSTRUCT):\n    \"\"\"certtransblob\"\"\"\n\n    structure = (\n        (\"cb\", ULONG),\n        (\"pb\", PBYTE),\n    )\n\n\n# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7\nclass CertServerRequest(NDRCALL):\n    \"\"\"certserver request\"\"\"\n\n    opnum = 0\n    structure = (\n        (\"dwFlags\", DWORD),\n        (\"pwszAuthority\", LPWSTR),\n        (\"pdwRequestId\", DWORD),\n        (\"pctbAttribs\", CERTTRANSBLOB),\n        (\"pctbRequest\", CERTTRANSBLOB),\n    )\n\n\n# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7\nclass CertServerRequestResponse(NDRCALL):\n    \"\"\"certserverresponse\"\"\"\n\n    structure = (\n        (\"pdwRequestId\", DWORD),\n        (\"pdwDisposition\", ULONG),\n        (\"pctbCert\", CERTTRANSBLOB),\n        (\"pctbEncodedCert\", CERTTRANSBLOB),\n        (\"pctbDispositionMessage\", CERTTRANSBLOB),\n    )\n\n\nclass Request:\n    \"\"\"request\"\"\"\n\n    # pylint: disable=c0103\n    def __init__(\n        self,\n        target: Target = None,\n        ca: str = None,\n        template: str = None,\n        alt: str = None,\n        debug=False,\n        do_kerberos=False,\n        **kwargs\n    ):\n        self.target = target\n        self.ca = ca\n        self.template = template\n        self.alt_name = alt\n        self.request_id = 0\n        self.verbose = debug\n        self.kwargs = kwargs\n        self.do_kerberos = do_kerberos\n        self.dce = get_dce_rpc(\n            MSRPC_UUID_ICPR,\n            r\"\\pipe\\cert\",\n            self.target,\n            timeout=self.target.timeout,\n            verbose=self.verbose,\n            do_kerberos=self.do_kerberos,\n        )\n\n    def get_cert(self, csr: bytes) -> bytes:\n        \"\"\"get cert\"\"\"\n        csr = csr_pem_to_der(csr)\n\n        attributes = [\"CertificateTemplate:%s\" % self.template]\n\n        if self.alt_name is not None:\n            attributes.append(\"SAN:upn=%s\" % self.alt_name)\n\n        attributes = checkNullString(\"\\n\".join(attributes)).encode(\"utf-16le\")\n        pctb_attribs = CERTTRANSBLOB()\n        pctb_attribs[\"cb\"] = len(attributes)\n        pctb_attribs[\"pb\"] = attributes\n\n        pctb_request = CERTTRANSBLOB()\n        pctb_request[\"cb\"] = len(csr)\n        pctb_request[\"pb\"] = csr\n\n        request = CertServerRequest()\n        request[\"dwFlags\"] = 0\n        request[\"pwszAuthority\"] = checkNullString(self.ca)\n        request[\"pdwRequestId\"] = self.request_id\n        request[\"pctbAttribs\"] = pctb_attribs\n        request[\"pctbRequest\"] = pctb_request\n\n        logging.info(\"Requesting certificate\")\n\n        response = self.dce.request(request)\n\n        error_code = response[\"pdwDisposition\"]\n        request_id = response[\"pdwRequestId\"]\n\n        if error_code == 3:\n            logging.info(\"Successfully requested certificate\")\n        elif error_code == 5:\n            logging.warning(\"Certificate request is pending approval\")\n        else:\n            error_msg = translate_error_code(error_code)\n            if \"unknown error code\" in error_msg:\n                logging.error(\n                    \"Got unknown error while trying to request certificate: (%s): %s\"\n                    % (\n                        error_msg,\n                        b\"\".join(response[\"pctbDispositionMessage\"][\"pb\"]).decode(\n                            \"utf-16le\"\n                        ),\n                    )\n                )\n            else:\n                logging.error(\n                    \"Got error while trying to request certificate: %s\" % error_msg\n                )\n\n        logging.info(\"Request ID is %d\" % request_id)\n\n        return der_to_pem(b\"\".join(response[\"pctbEncodedCert\"][\"pb\"]))\n"
  },
  {
    "path": "examples/ca_handler/ms_wcce/rpc.py",
    "content": "\"\"\"rpc.py\"\"\"\n\n# pylint: disable=C0209, C0415, E0401, R0913, W1201\nimport logging\nfrom impacket import uuid\nfrom impacket.dcerpc.v5 import epm, rpcrt, transport\nfrom examples.ca_handler.ms_wcce.target import Target\n\n\ndef get_dce_rpc_from_string_binding(\n    string_binding: str,\n    target: Target,\n    timeout: int = 5,\n    target_ip: str = None,\n    remote_name: str = None,\n    auth_level: int = rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,\n    do_kerberos=False,\n) -> rpcrt.DCERPC_v5:\n    \"\"\"get dce from rpc\"\"\"\n    if target_ip is None:\n        target_ip = target.target_ip\n    if remote_name is None:\n        remote_name = target.remote_name\n\n    target.do_kerberos = do_kerberos\n\n    rpctransport = transport.DCERPCTransportFactory(string_binding)\n\n    rpctransport.setRemoteHost(target_ip)\n    rpctransport.setRemoteName(remote_name)\n\n    rpctransport.set_connect_timeout(timeout)\n    rpctransport.set_kerberos(target.do_kerberos, kdcHost=target.dc_ip)\n\n    rpctransport.set_credentials(\n        target.username,\n        target.password,\n        target.domain,\n        target.lmhash,\n        target.nthash,\n        TGS=None,\n    )\n\n    dce = rpctransport.get_dce_rpc()\n    dce.set_auth_level(auth_level)\n\n    if target.do_kerberos is True:\n        dce.set_auth_type(rpcrt.RPC_C_AUTHN_GSS_NEGOTIATE)\n\n    return dce\n\n\ndef get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5):\n    \"\"\"get endpoint\"\"\"\n    string_binding = r\"ncacn_ip_tcp:%s[135]\" % target\n    rpctransport = transport.DCERPCTransportFactory(string_binding)\n    rpctransport.set_connect_timeout(timeout)\n    dce = rpctransport.get_dce_rpc()\n    logging.debug(\n        \"Trying to resolve dynamic endpoint %s\" % repr(uuid.bin_to_string(interface))\n    )\n    try:\n        dce.connect()\n    except Exception as err_:\n        logging.warning(\"Failed to connect to endpoint mapper: %s\" % err_)\n        return None\n    try:\n        endpoint = epm.hept_map(target, interface, protocol=\"ncacn_ip_tcp\", dce=dce)\n        logging.debug(\n            \"Resolved dynamic endpoint %s to %s\"\n            % (repr(uuid.bin_to_string(interface)), repr(endpoint))\n        )\n        return endpoint\n    except Exception:\n        logging.debug(\n            \"Failed to resolve dynamic endpoint %s\"\n            % repr(uuid.bin_to_string(interface))\n        )\n        return None\n\n\ndef get_dce_rpc(\n    interface: bytes,\n    named_pipe: str,\n    target: Target,\n    timeout=5,\n    verbose=False,\n    do_kerberos=False,\n    auth_level_np: int = rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,\n    auth_level_dyn: int = rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,\n) -> rpcrt.DCERPC_v5:\n    \"\"\"get dce rpc\"\"\"\n\n    def _try_binding(string_binding: str, auth_level: int) -> rpcrt.DCERPC_v5:\n        dce = get_dce_rpc_from_string_binding(\n            string_binding,\n            target,\n            timeout,\n            auth_level=auth_level,\n            do_kerberos=do_kerberos,\n        )\n        logging.debug(\"Trying to connect to endpoint: %s\" % string_binding)\n        try:\n            dce.connect()\n        except Exception as err_:\n            if verbose:\n                logging.warning(\n                    \"Failed to connect to endpoint %s: %s\" % (string_binding, err_)\n                )\n            return None\n\n        logging.debug(\"Connected to endpoint: %s\" % string_binding)\n\n        dce.bind(interface)\n\n        return dce\n\n    def _try_np() -> rpcrt.DCERPC_v5:\n        # Try named pipe\n        string_binding = \"ncacn_np:%s[%s]\" % (target.target_ip, named_pipe)\n        return _try_binding(string_binding, auth_level=auth_level_np)\n\n    def _try_dyn() -> rpcrt.DCERPC_v5:\n        string_binding = get_dynamic_endpoint(interface, target.target_ip, timeout)\n        if string_binding is None:\n            # Possible errors:\n            # - TCP Port 135 is firewalled off\n            # - CertSvc is not running\n            logging.error(\"Failed to get dynamic TCP endpoint for CertSvc\")\n            return None\n\n        dce = _try_binding(string_binding, auth_level=auth_level_dyn)\n        return dce\n\n    for method in [_try_np, _try_dyn]:\n        dce = method()\n        if dce is not None:\n            return dce\n\n    return None\n"
  },
  {
    "path": "examples/ca_handler/ms_wcce/target.py",
    "content": "\"\"\"target class\"\"\"\n\n# pylint: disable=C0209, C0415, R0913, W1201\nimport logging\nimport socket\nfrom dns.resolver import Resolver\n\n\ndef is_ip(hostname: str) -> bool:\n    \"\"\"check if sring is an ip\"\"\"\n    try:\n        # Check if hostname is an IP\n        socket.inet_aton(hostname)\n        result = True\n    except Exception:\n        result = False\n    return result\n\n\nclass DnsResolver:\n    \"\"\"DNS resolver class\"\"\"\n\n    def __init__(self):\n        self.resolver = Resolver()\n\n        self.mappings = {}\n\n    @staticmethod\n    def from_options(options, target) -> \"DnsResolver\":\n        \"\"\"setup resolver object from given options\"\"\"\n        self = DnsResolver()\n\n        # We can't put all possible nameservers in the list of nameservers, since\n        # the resolver will fail if one of them fails\n        nameserver = options.ns\n        if nameserver is None:\n            nameserver = target.dc_ip\n\n        if nameserver is not None:\n            self.resolver.nameservers = [nameserver]\n        # pylint: disable=W0201\n        self.use_tcp = options.dns_tcp\n\n        return self\n\n    @staticmethod\n    def create(\n        target: \"Target\" = None, ns_: str = None, dns_tcp: bool = False\n    ) -> \"DnsResolver\":\n        \"\"\"setup resolver object without options\"\"\"\n        self = DnsResolver()\n\n        # We can't put all possible nameservers in the list of nameservers, since\n        # the resolver will fail if one of them fails\n        nameserver = ns_\n        if nameserver is None:\n            nameserver = target.dc_ip\n\n        if nameserver is not None:\n            self.resolver.nameservers = [nameserver]\n\n        # pylint: disable=W0201\n        self.use_tcp = dns_tcp\n\n        return self\n\n    def resolve(self, hostname: str) -> str:\n        \"\"\"Try to resolve the hostname with DNS first, then try a local resolve\"\"\"\n        if hostname in self.mappings:\n            logging.debug(\n                \"Resolved %s from cache: %s\" % (repr(hostname), self.mappings[hostname])\n            )\n            return self.mappings[hostname]\n\n        if is_ip(hostname):\n            return hostname\n\n        ip_addr = None\n        if self.resolver.nameservers[0] is None:\n            logging.debug(\"Trying to resolve %s locally\" % repr(hostname))\n        else:\n            logging.debug(\n                \"Trying to resolve %s at %s\"\n                % (repr(hostname), repr(self.resolver.nameservers[0]))\n            )\n        try:\n            answers = self.resolver.resolve(hostname, tcp=self.use_tcp)\n            if len(answers) == 0:\n                raise SystemError()\n\n            ip_addr = answers[0].to_text()\n        except Exception as err_:\n            logging.debug(\"Error resolving %s : %s\" % (repr(hostname), err_))\n\n        if ip_addr is None:\n            try:\n                ip_addr = socket.gethostbyname(hostname)\n            except Exception:\n                ip_addr = None\n\n        if ip_addr is None:\n            logging.warning(\"Failed to resolve: %s\" % hostname)\n            return hostname\n\n        self.mappings[hostname] = ip_addr\n        return ip_addr\n\n\nclass Target:\n    \"\"\"target class\"\"\"\n\n    def __init__(\n        self,\n        domain: str = None,\n        username: str = None,\n        password: str = None,\n        target_ip: str = None,\n        remote_name: str = None,\n        no_pass: bool = False,\n        dc_ip: str = None,\n        ns_: str = None,\n        dns_tcp: bool = False,\n        timeout: int = 5,\n    ):\n        if domain is None:\n            domain = \"\"\n\n        if password == \"\" and username != \"\" and no_pass is not True:\n            from getpass import getpass\n\n            password = getpass(\"Password:\")\n\n        lmhash = nthash = \"\"\n\n        self.domain = domain\n        self.username = username\n        self.password = password\n        self.remote_name = remote_name\n        self.lmhash = lmhash\n        self.nthash = nthash\n        self.dc_ip = dc_ip\n        self.timeout = timeout\n\n        if ns_ is None:\n            ns_ = dc_ip\n\n        if is_ip(remote_name):\n            target_ip = remote_name\n\n        self.resolver = DnsResolver.create(self, ns_=ns_, dns_tcp=dns_tcp)\n\n        self.target_ip = target_ip\n        if self.target_ip is None and remote_name is not None:\n            self.target_ip = self.resolver.resolve(remote_name)\n\n    def __repr__(self) -> str:\n        return \"<Target (%s)>\" % repr(self.__dict__)\n"
  },
  {
    "path": "examples/ca_handler/mscertsrv_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ca handler for  Microsoft Webenrollment service (certsrv)\"\"\"\nfrom __future__ import print_function\nimport os\nimport textwrap\nimport json\nfrom typing import List, Tuple, Dict\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives.serialization.pkcs7 import (\n    load_pem_pkcs7_certificates,\n    load_der_pkcs7_certificates,\n)\n\n# pylint: disable=e0401, e0611\nfrom examples.ca_handler.certsrv import Certsrv\nfrom acme_srv.helper import (\n    b64_url_recode,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_profile_load,\n    convert_byte_to_string,\n    convert_string_to_byte,\n    eab_profile_header_info_check,\n    enrollment_config_log,\n    handler_config_check,\n    header_info_get,\n    load_config,\n    proxy_check,\n)  # pylint: disable=e0401\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.host = None\n        self.url = None\n        self.user = None\n        self.password = None\n        self.auth_method = \"basic\"\n        self.ca_bundle = False\n        self.template = None\n        self.krb5_config = None\n        self.proxy = None\n        self.header_info_field = False\n        self.verify = True\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _check_credentials(self, ca_server: object) -> bool:\n        \"\"\"check creadentials\"\"\"\n        self.logger.debug(\"CAhandler.__check_credentials()\")\n        auth_check = ca_server.check_credentials()\n        self.logger.debug(\"CAhandler.__check_credentials() ended with %s\", auth_check)\n        return auth_check\n\n    def _cert_bundle_create(\n        self, ca_pem: str = None, cert_raw: str = None\n    ) -> Tuple[str, str, str]:\n        \"\"\"create bundle\"\"\"\n        self.logger.debug(\"CAhandler._cert_bundle_create()\")\n\n        error = None\n        cert_bundle = None\n\n        if ca_pem and cert_raw:\n            cert_bundle = cert_raw + ca_pem\n            cert_raw = cert_raw.replace(\"-----BEGIN CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"-----END CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"\\n\", \"\")\n        else:\n            self.logger.error(\n                \"Failed to bundle certificates: missing ca_pem or cert_raw.\"\n            )\n            error = \"Certificate bundling failed: missing CA certificate or issued certificate.\"\n\n        return (error, cert_bundle, cert_raw)\n\n    def _config_headerinfo_load(self, config_dic: Dict[str, str]):\n        \"\"\"load parameters\"\"\"\n        self.logger.debug(\"_config_header_info()\")\n\n        if (\n            \"Order\" in config_dic\n            and \"header_info_list\" in config_dic[\"Order\"]\n            and config_dic[\"Order\"][\"header_info_list\"]\n        ):\n            try:\n                self.header_info_field = json.loads(\n                    config_dic[\"Order\"][\"header_info_list\"]\n                )[0]\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse header_info_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"_config_header_info() ended\")\n\n    def _config_user_load(self, config_dic: Dict[str, str]):\n        \"\"\"load username\"\"\"\n        self.logger.debug(\"CAhandler._config_user_load()\")\n\n        if \"user_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.user = os.environ[config_dic.get(\"CAhandler\", \"user_variable\")]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load user_variable from environment: %s\", err\n                )\n        if \"user\" in config_dic[\"CAhandler\"]:\n            if self.user:\n                self.logger.info(\"Overwrite user\")\n            self.user = config_dic.get(\"CAhandler\", \"user\")\n\n        self.logger.debug(\"CAhandler._config_user_load() ended\")\n\n    def _config_password_load(self, config_dic: Dict[str, str]):\n        \"\"\"load username\"\"\"\n        self.logger.debug(\"CAhandler._config_password_load()\")\n\n        if \"password_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.password = os.environ[\n                    config_dic.get(\"CAhandler\", \"password_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load password_variable from environment: %s\", err\n                )\n        if \"password\" in config_dic[\"CAhandler\"]:\n            if self.password:\n                self.logger.info(\"Overwrite password\")\n            self.password = config_dic.get(\"CAhandler\", \"password\")\n\n        self.logger.debug(\"CAhandler._config_password_load() ended\")\n\n    def _config_hostname_load(self, config_dic: Dict[str, str]):\n        \"\"\"load hostname\"\"\"\n        self.logger.debug(\"CAhandler._config_hostname_load()\")\n\n        if \"host_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.host = os.environ[config_dic.get(\"CAhandler\", \"host_variable\")]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load host_variable from environment: %s\", err\n                )\n        if \"host\" in config_dic[\"CAhandler\"]:\n            if self.host:\n                self.logger.info(\"Overwrite host\")\n            self.host = config_dic.get(\"CAhandler\", \"host\")\n        self.logger.debug(\"CAhandler._config_hostname_load() ended\")\n\n    def _config_url_load(self, config_dic: Dict[str, str]):\n        if \"url_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.url = os.environ[config_dic.get(\"CAhandler\", \"url_variable\")]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load url_variable from environment: %s\", err\n                )\n        if \"url\" in config_dic[\"CAhandler\"]:\n            if self.url:\n                self.logger.info(\"Overwrite url\")\n            self.url = config_dic.get(\"CAhandler\", \"url\")\n\n        self.logger.debug(\"CAhandler._config_url_load() ended\")\n\n    def _config_parameters_load(self, config_dic: Dict[str, str]):\n        \"\"\"load hostname\"\"\"\n        self.logger.debug(\"CAhandler._config_parameters_load()\")\n\n        self.template = config_dic.get(\"CAhandler\", \"template\", fallback=self.template)\n        if \"auth_method\" in config_dic[\"CAhandler\"] and config_dic[\"CAhandler\"][\n            \"auth_method\"\n        ] in [\"basic\", \"ntlm\", \"gssapi\"]:\n            self.auth_method = config_dic.get(\"CAhandler\", \"auth_method\")\n        # check if we get a ca bundle for verification\n        self.ca_bundle = config_dic.get(\n            \"CAhandler\", \"ca_bundle\", fallback=self.ca_bundle\n        )\n        self.krb5_config = config_dic.get(\n            \"CAhandler\", \"krb5_config\", fallback=self.krb5_config\n        )\n        self.verify = config_dic.getboolean(\"CAhandler\", \"verify\", fallback=True)\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_parameters_load() ended\")\n\n    def _config_proxy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load hostname\"\"\"\n        self.logger.debug(\"CAhandler._config_proxy_load()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                proxy_list = json.loads(config_dic.get(\"DEFAULT\", \"proxy_server_list\"))\n                proxy_server = proxy_check(self.logger, self.host, proxy_list)\n                self.proxy = {\"http\": proxy_server, \"https\": proxy_server}\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load proxy_server_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"CAhandler._config_proxy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n            # load parameters from config dic\n            self._config_hostname_load(config_dic)\n            self._config_url_load(config_dic)\n            self._config_user_load(config_dic)\n            self._config_password_load(config_dic)\n            self._config_parameters_load(config_dic)\n            # load profiling\n            self.eab_profiling, self.eab_handler = config_eab_profile_load(\n                self.logger, config_dic\n            )\n            # load profiles\n            self.profiles = config_profile_load(self.logger, config_dic)\n            self._config_headerinfo_load(config_dic)\n\n        # load proxy config\n        self._config_proxy_load(config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _pkcs7_to_pem(self, pkcs7_content: str, outform: str = \"string\") -> List[str]:\n        \"\"\"convert pkcs7 to pem\"\"\"\n        self.logger.debug(\"CAhandler._pkcs7_to_pem()\")\n\n        # Define loading strategies in order of preference\n        loading_strategies = [\n            # Strategy 1: Load as PEM directly\n            lambda content: load_pem_pkcs7_certificates(\n                convert_string_to_byte(content)\n            ),\n            # Strategy 2: Replace CERTIFICATE with PKCS7 tag and load as PEM\n            lambda content: load_pem_pkcs7_certificates(\n                convert_string_to_byte(content.replace(\"CERTIFICATE\", \"PKCS7\"))\n            ),\n            # Strategy 3: Load as DER\n            lambda content: load_der_pkcs7_certificates(content),\n        ]\n\n        pkcs7_obj = None\n        last_error = None\n\n        for i, strategy in enumerate(loading_strategies):\n            try:\n                pkcs7_obj = strategy(pkcs7_content)\n                if i == 1:  # Log only for the tag replacement strategy\n                    self.logger.error(\n                        \"PKCS7-TAG not found, updated content successfully\"\n                    )\n                break\n            except Exception as err:\n                last_error = err\n                if i == 0:\n                    self.logger.error(\"PKCS7-TAG not found updating content...\")\n                elif i == 1:\n                    self.logger.debug(\n                        \"CAhandler._pkcs7_to_pem(): load pem failed. Try der...\"\n                    )\n\n        if pkcs7_obj is None:\n            self.logger.error(\n                \"All PKCS7 loading strategies failed. Last error: %s\", last_error\n            )\n            raise last_error\n\n        # Convert certificates to PEM format\n        cert_pem_list = [\n            convert_byte_to_string(cert.public_bytes(serialization.Encoding.PEM))\n            for cert in pkcs7_obj\n        ]\n\n        # Define output format\n        output_formats = {\n            \"string\": lambda certs: \"\".join(certs),\n            \"list\": lambda certs: certs,\n        }\n\n        result = output_formats.get(outform, lambda _: None)(cert_pem_list)\n\n        self.logger.debug(\"Certificate._pkcs7_to_pem() ended\")\n        return result\n\n    def _template_name_get(self, csr: str) -> str:\n        \"\"\"get templaate from csr\"\"\"\n        self.logger.debug(\"CAhandler._template_name_get(%s)\", csr)\n        template_name = None\n\n        # parse profileid from http_header\n        header_info = header_info_get(self.logger, csr=csr)\n        if header_info:\n            try:\n                header_info_dic = json.loads(header_info[-1][\"header_info\"])\n                if self.header_info_field in header_info_dic:\n                    for ele in header_info_dic[self.header_info_field].split(\" \"):\n                        if \"template\" in ele.lower():\n                            template_name = ele.split(\"=\")[1]\n                            break\n            except Exception as err:\n                self.logger.error(\"Failed to parse template from header_info: %s\", err)\n\n        self.logger.debug(\n            \"CAhandler._template_name_get() ended with: %s\", template_name\n        )\n        return template_name\n\n    def _csr_process(self, ca_server, csr: str) -> Tuple[str, str, str]:\n\n        # recode csr\n        csr = textwrap.fill(b64_url_recode(self.logger, csr), 64) + \"\\n\"\n\n        # get ca_chain\n        try:\n            ca_pkcs7 = convert_byte_to_string(ca_server.get_chain(encoding=\"b64\"))\n            ca_pem = self._pkcs7_to_pem(ca_pkcs7)\n            # replace crlf with lf\n            # ca_pem = ca_pem.replace('\\r\\n', '\\n')\n        except Exception as err_:\n            ca_pem = None\n            self.logger.error(\"Failed to get CA certificate chain: %s\", err_)\n\n        try:\n            cert_p2b = ca_server.get_cert(csr, self.template)\n            cert_raw = convert_byte_to_string(cert_p2b)\n            # replace crlf with lf\n            cert_raw = cert_raw.replace(\"\\r\\n\", \"\\n\")\n        except Exception as err_:\n            cert_raw = None\n            error = str(err_)\n            self.logger.error(\"Failed to enroll certificate from CA: %s\", err_)\n\n        # create bundle\n        if cert_raw:\n            (error, cert_bundle, cert_raw) = self._cert_bundle_create(ca_pem, cert_raw)\n        else:\n            cert_bundle = None\n\n        return (error, cert_bundle, cert_raw)\n\n    def _parameter_overwrite(self, _csr: str):\n        \"\"\"overwrite overwrite krb5.conf or user-template\"\"\"\n        if self.krb5_config:\n            self.logger.info(\"Load krb5config from %s\", self.krb5_config)\n            os.environ[\"KRB5_CONFIG\"] = self.krb5_config\n\n    def _enroll(self, csr: str) -> Tuple[str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n        # setup certserv\n        ca_server = Certsrv(\n            self.host,\n            self.url,\n            self.user,\n            self.password,\n            self.auth_method,\n            self.ca_bundle,\n            verify=self.verify,\n            proxies=self.proxy,\n        )\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        # check connection and credentials\n        auth_check = self._check_credentials(ca_server)\n\n        if self.enrollment_config_log:\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if auth_check:\n            # enroll certificate\n            (error, cert_bundle, cert_raw) = self._csr_process(ca_server, csr)\n        else:\n            self.logger.error(\"Connection or credential check failed for CA server.\")\n            error = \"Connection or Credentialcheck failed.\"\n\n        self.logger.debug(\"CAhandler._enroll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, bool]:\n        \"\"\"enroll certificate from via MS certsrv\"\"\"\n        self.logger.debug(\"CAhandler.enroll(%s)\", self.template)\n        cert_bundle = None\n        error = None\n        cert_raw = None\n\n        self._parameter_overwrite(csr)\n\n        if (self.host or self.url) and self.user and self.password and self.template:\n\n            # check for eab profiling and header_info\n            error = eab_profile_header_info_check(self.logger, self, csr, \"template\")\n            if not error:\n                # enroll certificate\n                (error, cert_bundle, cert_raw) = self._enroll(csr)\n            else:\n                self.logger.error(\"EAB profile check failed: %s\", error)\n\n        else:\n            self.logger.error(\"Configuration incomplete\")\n            error = \"Config incomplete\"\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = handler_config_check(\n            self.logger, self, [\"host\", \"user\", \"password\", \"template\"]\n        )\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, _cert: str, _rev_reason: str, _rev_date: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.tsg_id_lookup()\")\n        # get serial from pem file and convert to formated hex\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[int, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/mswcce_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"CA handler for Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE)\"\"\"\nfrom __future__ import print_function\nimport os\nimport json\nfrom typing import Tuple, Dict\n\n# pylint: disable=e0401, e0611\nfrom examples.ca_handler.ms_wcce.target import Target\nfrom examples.ca_handler.ms_wcce.request import Request\n\n# pylint: disable=E0401\nfrom acme_srv.helper import (\n    build_pem_file,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_profile_load,\n    convert_byte_to_string,\n    convert_string_to_byte,\n    eab_profile_header_info_check,\n    enrollment_config_log,\n    handler_config_check,\n    header_info_get,\n    load_config,\n    proxy_check,\n    radomize_parameter_list,\n)\n\n\nclass CAhandler(object):\n    \"\"\"MS-WCCE CA handler\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        self.logger = logger\n        self.host = None\n        self.user = None\n        self.password = None\n        self.template = None\n        self.proxy = None\n        self.target_domain = None\n        self.domain_controller = None\n        self.ca_name = None\n        self.ca_bundle = False\n        self.use_kerberos = False\n        self.header_info_field = None\n        self.timeout = 5\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"close the connection at the end of the context\"\"\"\n\n    def _config_headerinfo_load(self, config_dic: Dict[str, str]):\n        \"\"\"load parameters\"\"\"\n        self.logger.debug(\"_config_header_info()\")\n\n        if (\n            \"Order\" in config_dic\n            and \"header_info_list\" in config_dic[\"Order\"]\n            and config_dic[\"Order\"][\"header_info_list\"]\n        ):\n            try:\n                self.header_info_field = json.loads(\n                    config_dic[\"Order\"][\"header_info_list\"]\n                )[0]\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to parse header_info_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"_config_header_info() ended\")\n\n    def _config_host_load(self, config_dic: Dict[str, str]):\n        \"\"\"load host variable\"\"\"\n        self.logger.debug(\"CAhandler._config_host_load()\")\n\n        if \"host_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.host = os.environ[config_dic.get(\"CAhandler\", \"host_variable\")]\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load host variable from environment: %s\", err\n                )\n        if \"host\" in config_dic[\"CAhandler\"]:\n            if self.host:\n                self.logger.info(\"Overwrite host\")\n            self.host = config_dic.get(\"CAhandler\", \"host\")\n\n        self.logger.debug(\"CAhandler._config_host_load() ended\")\n\n    def _config_credentials_load(self, config_dic: Dict[str, str]):\n        \"\"\"load host variable\"\"\"\n        self.logger.debug(\"CAhandler._config_credentials_load()\")\n\n        if \"user_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.user = os.environ[config_dic.get(\"CAhandler\", \"user_variable\")]\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load user variable from environment: %s\", err\n                )\n        if \"user\" in config_dic[\"CAhandler\"]:\n            if self.user:\n                self.logger.info(\"Overwrite user\")\n            self.user = config_dic.get(\"CAhandler\", \"user\")\n\n        if \"password_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.password = os.environ[\n                    config_dic.get(\"CAhandler\", \"password_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load password variable from environment: %s\", err\n                )\n        if \"password\" in config_dic[\"CAhandler\"]:\n            if self.password:\n                self.logger.info(\"Overwrite password\")\n            self.password = config_dic.get(\"CAhandler\", \"password\")\n\n        self.logger.debug(\"CAhandler._config_credentials_load() ended\")\n\n    def _config_parameters_load(self, config_dic: Dict[str, str]):\n        \"\"\"load parameters\"\"\"\n        self.logger.debug(\"CAhandler._config_parameters_load()\")\n\n        if \"domain_controller\" in config_dic[\"CAhandler\"]:\n            self.domain_controller = config_dic.get(\"CAhandler\", \"domain_controller\")\n        elif \"dns_server\" in config_dic[\"CAhandler\"]:\n            self.domain_controller = config_dic.get(\"CAhandler\", \"dns_server\")\n\n        self.target_domain = config_dic.get(\"CAhandler\", \"target_domain\", fallback=None)\n        self.ca_name = config_dic.get(\"CAhandler\", \"ca_name\", fallback=None)\n        self.ca_bundle = config_dic.get(\"CAhandler\", \"ca_bundle\", fallback=None)\n        self.template = config_dic.get(\"CAhandler\", \"template\", fallback=None)\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        try:\n            self.timeout = config_dic.getint(\"CAhandler\", \"timeout\", fallback=5)\n        except Exception as err_:\n            self.logger.warning(\n                \"Failed to parse 'timeout' from configuration. Using default value 5. Error: %s\",\n                err_,\n            )\n            self.timeout = 5\n\n        try:\n            self.use_kerberos = config_dic.getboolean(\n                \"CAhandler\", \"use_kerberos\", fallback=False\n            )\n        except Exception as err_:\n            self.logger.warning(\n                \"Failed to parse 'use_kerberos' from configuration. Using default value False. Error: %s\",\n                err_,\n            )\n\n        self.logger.debug(\"CAhandler._config_parameters_load()\")\n\n    def _config_proxy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load proxy settings\"\"\"\n        self.logger.debug(\"CAhandler._config_proxy_load()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                proxy_list = json.loads(config_dic.get(\"DEFAULT\", \"proxy_server_list\"))\n                proxy_server = proxy_check(self.logger, self.host, proxy_list)\n                self.proxy = {\"http\": proxy_server, \"https\": proxy_server}\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load proxy_server_list from configuration: %s\",\n                    err_,\n                )\n\n        self.logger.debug(\"CAhandler._config_proxy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n\n            self._config_host_load(config_dic)\n            self._config_credentials_load(config_dic)\n            self._config_parameters_load(config_dic)\n            # load profiling\n            self.eab_profiling, self.eab_handler = config_eab_profile_load(\n                self.logger, config_dic\n            )\n            # load profiles\n            self.profiles = config_profile_load(self.logger, config_dic)\n            self._config_headerinfo_load(config_dic)\n\n        self._config_proxy_load(config_dic)\n        radomize_parameter_list(self.logger, self, [\"host\", \"ca_name\", \"ca_bundle\"])\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _file_load(self, bundle: str) -> str:\n        \"\"\"load file\"\"\"\n        file_ = None\n        try:\n            with open(bundle, \"r\", encoding=\"utf-8\") as fso:\n                file_ = fso.read()\n        except Exception as err_:\n            self.logger.error(\"Could not load file '%s'. Error: %s\", bundle, err_)\n        return file_\n\n    def request_create(self) -> Request:\n        \"\"\"create request object\"\"\"\n        self.logger.debug(\"CAhandler.request_create()\")\n\n        if self.enrollment_config_log:\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        target = Target(\n            domain=self.target_domain,\n            username=self.user,\n            password=self.password,\n            remote_name=self.host,\n            dc_ip=self.domain_controller,\n            timeout=self.timeout,\n        )\n        request = Request(\n            target=target,\n            ca=self.ca_name,\n            template=self.template,\n            do_kerberos=self.use_kerberos,\n        )\n\n        self.logger.debug(\"CAhandler.request_create() ended\")\n        return request\n\n    def _template_name_get(self, csr: str) -> str:\n        \"\"\"get templaate from csr\"\"\"\n        self.logger.debug(\"CAhandler._template_name_get(%s)\", csr)\n        template_name = None\n\n        # parse profileid from http_header\n        header_info = header_info_get(self.logger, csr=csr)\n        if header_info:\n            try:\n                header_info_dic = json.loads(header_info[-1][\"header_info\"])\n                if self.header_info_field in header_info_dic:\n                    for ele in header_info_dic[self.header_info_field].split(\" \"):\n                        if \"template\" in ele.lower():\n                            template_name = ele.split(\"=\")[1]\n                            break\n            except Exception as err:\n                self.logger.error(\"Failed to parse template from header info: %s\", err)\n\n        self.logger.debug(\n            \"CAhandler._template_name_get() ended with: %s\", template_name\n        )\n        return template_name\n\n    def _enroll(self, csr: str) -> Tuple[str, str, str]:\n        \"\"\"enroll certificate via MS-WCCE\"\"\"\n        self.logger.debug(\"CAhandler._enroll(%s)\", self.template)\n        error = None\n        cert_raw = None\n        cert_bundle = None\n\n        # create request\n        request = self.request_create()\n\n        # reformat csr\n        csr = build_pem_file(self.logger, None, csr, 64, True)\n\n        # pylint: disable=W0511\n        # currently getting certificate chain is not supported\n        ca_pem = self._file_load(self.ca_bundle)\n\n        try:\n            # request certificate\n            cert_raw = convert_byte_to_string(\n                request.get_cert(convert_string_to_byte(csr))\n            )\n            # replace crlf with lf\n            cert_raw = cert_raw.replace(\"\\r\\n\", \"\\n\")\n        except Exception as err:\n            cert_raw = None\n            self.logger.error(\"Enrollment failed with error: %s\", err)\n            error = \"Could not get certificate from CA server\"\n\n        if not error and cert_raw:\n            if ca_pem:\n                cert_bundle = cert_raw + ca_pem\n            else:\n                cert_bundle = cert_raw\n            cert_raw = cert_raw.replace(\"-----BEGIN CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"-----END CERTIFICATE-----\\n\", \"\")\n            cert_raw = cert_raw.replace(\"\\n\", \"\")\n        else:\n            self.logger.error(\n                \"Certificate bundling failed: CA certificate or issued certificate is missing.\"\n            )\n            error = \"Certificate bundling failed: CA certificate or issued certificate is missing.\"\n\n        self.logger.debug(\"CAhandler._enroll() ended with error: %s\", error)\n        return error, cert_raw, cert_bundle\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate via MS-WCCE\"\"\"\n        self.logger.debug(\"CAhandler.enroll(%s)\", self.template)\n        cert_bundle = None\n        error = None\n        cert_raw = None\n\n        if not (self.host and self.user and self.password and self.template):\n            self.logger.error(\n                \"Configuration incomplete: host, user, password, or template is missing.\"\n            )\n            return (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            )\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"template\")\n\n        if not error:\n            # enroll certificate\n            (error, cert_raw, cert_bundle) = self._enroll(csr)\n\n        else:\n            self.logger.error(\"EAB profile check failed: %s\", error)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = handler_config_check(\n            self.logger,\n            self,\n            [\"host\", \"user\", \"password\", \"template\", \"ca_name\", \"target_domain\"],\n        )\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, _cert: str, _rev_reason: str, _rev_date: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.tsg_id_lookup()\")\n        # get serial from pem file and convert to formated hex\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/nclm_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"ca handler for \"NetGuard Certificate Lifecycle Manager\" via REST-API class\"\"\"\nfrom __future__ import print_function\nimport os\nimport time\nimport json\nfrom typing import List, Tuple, Dict\nimport requests\n\n# pylint: disable=e0401, r0913\nfrom acme_srv.helper import (\n    b64_encode,\n    b64_url_recode,\n    build_pem_file,\n    cert_serial_get,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    convert_string_to_byte,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    error_dic_get,\n    header_info_get,\n    load_config,\n    parse_url,\n    proxy_check,\n    uts_now,\n    uts_to_date_utc,\n)\n\n\nclass CAhandler(object):\n    \"\"\"CA  handler\"\"\"\n\n    def __init__(self, _debug=None, logger=None):\n        self.logger = logger\n        self.api_host = None\n        self.nclm_version = None\n        self.api_version = \"/v2\"\n        self.ca_bundle = True\n        self.credential_dic = {\"api_user\": None, \"api_password\": None}\n        self.container_info_dic = {\"name\": None, \"id\": None}\n        self.template_info_dic = {\"name\": None, \"id\": None}\n        self.headers = None\n        self.ca_name = None\n        self.error = None\n        self.wait_interval = 5\n        self.proxy = None\n        self.request_timeout = 20\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.api_host:\n            self._config_load()\n            self._config_check()\n        if not self.headers and not self.error:\n            self._login()\n        if not self.container_info_dic[\"id\"] and not self.error:\n            self._container_id_lookup()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"generic wrapper for an API post call\"\"\"\n        self.logger.debug(\"CAhandler._api_post()\")\n        try:\n            response = requests.post(\n                url=url,\n                json=data,\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            )\n            try:\n                api_response = response.json()\n            except Exception:\n                api_response = {\"status\": response.status_code}\n        except Exception as err_:\n            self.logger.error(\"API POST request failed: %s\", err_)\n            api_response = str(err_)\n\n        self.logger.debug(\"CAhandler._api_post() ended with: %s\", api_response)\n        return api_response\n\n    def _ca_id_get(self, ca_list: Dict[str, str]) -> int:\n        \"\"\"get ca_id\"\"\"\n        self.logger.debug(\"CAhandler._ca_id_get()\")\n        ca_id = None\n        if \"items\" in ca_list:\n            for ca_ in ca_list[\"items\"]:\n                # compare name or description field against config value\n                if \"name\" in ca_ and ca_[\"name\"] == self.ca_name:\n                    # pylint: disable=R1723\n                    if \"id\" in ca_:\n                        ca_id = ca_[\"id\"]\n                        break\n                    else:\n                        self.logger.error(\"CA response missing policyLinkId field.\")\n\n        self.logger.debug(\"CAhandler._ca_id_get() with %s\", ca_id)\n        return ca_id\n\n    def _ca_policylink_id_lookup(self) -> int:\n        \"\"\"lookup CA ID based on CA_name\"\"\"\n        self.logger.debug(\"CAhandler._ca_policylink_id_lookup()\")\n\n        # query CAs\n        ca_list = requests.get(\n            f'{self.api_host}{self.api_version}/containers/{self.container_info_dic[\"id\"]}/issuers',\n            headers=self.headers,\n            verify=self.ca_bundle,\n            proxies=self.proxy,\n            timeout=self.request_timeout,\n        ).json()\n        if \"items\" in ca_list:\n            ca_id = self._ca_id_get(ca_list)\n        else:\n            # log error\n            ca_id = None\n            self.logger.error(\"No CAs found in issuer response.\")\n\n        if not ca_id:\n            # log error\n            self.logger.error(\"No policy link ID found for CA name: %s\", self.ca_name)\n        self.logger.debug(\"CAhandler._ca_policylink_id_lookup() ended with: %s\", ca_id)\n        return ca_id\n\n    def _cert_enroll(self, csr: str, policylink_id: int) -> Tuple[str, str, str]:\n        \"\"\"enroll operation\"\"\"\n        self.logger.debug(\"CAhandler._cert_enroll()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        cert_id = None\n\n        # post csr\n        job_id = self._csr_post(csr, policylink_id)\n\n        if job_id:\n            cert_id = self._cert_id_get(job_id)\n            if cert_id:\n                (error, cert_bundle, cert_raw) = self._cert_bundle_build(cert_id)\n            else:\n                self.logger.error(\"Certificate ID lookup failed for job: %s\", job_id)\n                error = \"Certifcate_id lookup failed\"\n        else:\n            self.logger.error(\"Job ID lookup failed during certificate enrollment.\")\n            error = \"job_id lookup failed\"\n\n        self.logger.debug(\"CAhandler._cert_enroll() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw, cert_id)\n\n    def _csr_post(self, csr: str, policylink_id: int) -> Dict[str, str]:\n        \"\"\"post csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_post()\")\n\n        job_id = None\n        # build_pem_file\n        csr = build_pem_file(self.logger, None, csr, 64, True)\n        csr = b64_encode(self.logger, convert_string_to_byte(csr))\n        data_dic = {\"allowDuplicateCn\": True, \"request\": {\"pkcs10\": csr}}\n\n        # add template if correctly configured\n        if \"id\" in self.template_info_dic and self.template_info_dic[\"id\"]:\n            data_dic[\"template\"] = {\"id\": self.template_info_dic[\"id\"]}\n\n        response = self._api_post(\n            f\"{self.api_host}{self.api_version}/containers/{self.container_info_dic['id']}/issuers/{policylink_id}/csr\",\n            data_dic,\n        )\n\n        if \"id\" in response:\n            job_id = response[\"id\"]\n\n        self.logger.debug(\"CAhandler._csr_post() ended with: %s\", job_id)\n        return job_id\n\n    def _issuer_certid_get(self, cert_dic: Tuple[str, str]) -> Tuple[str, bool]:\n        \"\"\"get cert id of issuer\"\"\"\n        self.logger.debug(\"CAhandler._issuer_certid_get()\")\n\n        cert_id = None\n        issuer_loop = False\n\n        if (\n            isinstance(cert_dic, dict)\n            and \"urls\" in cert_dic\n            and \"issuer\" in cert_dic[\"urls\"]\n        ):\n            self.logger.debug(\n                \"CAhandler._cert_bundle_build() fetch issuer : %s\",\n                cert_dic[\"urls\"][\"issuer\"],\n            )\n            cert_dic = requests.get(\n                self.api_host + cert_dic[\"urls\"][\"issuer\"],\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n            if \"urls\" in cert_dic and \"certificate\" in cert_dic[\"urls\"]:\n                cert_id = cert_dic[\"urls\"][\"certificate\"].replace(\n                    \"/v2/certificates/\", \"\"\n                )\n                self.logger.debug(\n                    \"CAhandler._cert_bundle_build() fetch certificate for issuer-certid: %s\",\n                    cert_id,\n                )\n                issuer_loop = True\n\n        self.logger.debug(\"CAhandler._issuer_certid_get() ended with: %s\", cert_id)\n        return (cert_id, issuer_loop)\n\n    def _cert_bundle_build(self, cert_id: int) -> Tuple[str, str, str]:\n        \"\"\"download cert and create bundle\"\"\"\n        self.logger.debug(\"CAhandler._cert_bundle_build(%s)\", cert_id)\n        cert_bundle = \"\"\n        error = None\n        cert_raw = None\n        issuer_loop = True\n        count = 0\n\n        while issuer_loop:\n            # set issuer loop to False to avoid ending in an endless loop\n            issuer_loop = False\n            count += 1\n            self.logger.debug(\n                \"CAhandler._cert_bundle_build() fetch certificate for certid: %s\",\n                cert_id,\n            )\n\n            cert_dic = requests.get(\n                f\"{self.api_host}{self.api_version}/certificates/{cert_id}\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n            if \"der\" in cert_dic:\n                if count == 1:\n                    # get cert_raw\n                    cert_raw = cert_dic[\"der\"]\n\n                # build_pem_file\n                cert_bundle = build_pem_file(\n                    self.logger,\n                    existing=cert_bundle,\n                    certificate=cert_dic[\"der\"],\n                    wrap=True,\n                    csr=False,\n                )\n                cert_id, issuer_loop = self._issuer_certid_get(cert_dic)\n\n        # we need this for backwards compability\n        if cert_bundle == \"\":\n            cert_bundle = None\n\n        self.logger.debug(\"CAhandler._cert_bundle_build() ended\")\n        return (error, cert_bundle, cert_raw)\n\n    def _cert_id_get(self, job_id: int) -> int:\n        \"\"\"lookup get cert_id from enrollment job\"\"\"\n        self.logger.debug(\"CAhandler._cert_id_get(%s)\", job_id)\n\n        cert_id = None\n        # check job status\n        cnt = 0\n        while cnt < 10:\n            response = requests.get(\n                f\"{self.api_host}{self.api_version}/jobs/{job_id}\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n            if response.get(\"status\", None) == \"done\":\n                if (\n                    len(response.get(\"entities\", [])) > 0\n                    and \"ref\" in response[\"entities\"][0]\n                    and response[\"entities\"][0][\"ref\"].lower() == \"certificate\"\n                    and \"url\" in response[\"entities\"][0]\n                ):\n                    cert_id = response[\"entities\"][0][\"url\"].replace(\n                        \"/v2/certificates/\", \"\"\n                    )\n                    break\n                else:\n                    self.logger.error(\n                        \"Job completed but certificate reference is missing or malformed: %s\",\n                        response,\n                    )\n                    break\n\n            self.logger.debug(\n                \"CAhandler._cert_id_get() waiting for job to complete. Attempt: %s status: %s\",\n                cnt,\n                response.get(\"status\", None),\n            )\n            cnt += 1\n            time.sleep(self.wait_interval)\n\n        self.logger.debug(\"CAhandler._cert_id_get() ended with: %s\", cert_id)\n        return cert_id\n\n    def _certid_get_from_serial(self, cert_raw: str) -> List[str]:\n        \"\"\"get certificates\"\"\"\n        self.logger.debug(\"CAhandler._certid_get_from_serial()\")\n\n        cert_serial = cert_serial_get(self.logger, cert_raw, hexformat=True)\n\n        # search for certificate\n        try:\n            cert_list = requests.get(\n                f\"{self.api_host}{self.api_version}/certificates?freeText=={cert_serial}&containerId={self.container_info_dic['id']}\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err:\n            self.logger.error(\n                \"API request to fetch certificates got aborted with err: %s\",\n                err,\n            )\n            cert_list = []\n\n        if (\n            cert_list\n            and \"items\" in cert_list\n            and len(cert_list[\"items\"]) > 0\n            and \"id\" in cert_list[\"items\"][0]\n        ):\n            cert_id = cert_list[\"items\"][0][\"id\"]\n        else:\n            cert_id = None\n            self.logger.error(\n                \"Failed to retrieve certificate by serial: %s\",\n                cert_serial,\n            )\n\n        self.logger.debug(\n            \"CAhandler._certid_get_from_serial() ended with code: %s\", cert_id\n        )\n        return cert_id\n\n    def _cert_id_lookup(self, cert_raw: str) -> int:\n        \"\"\"get tracking id\"\"\"\n        self.logger.debug(\"CAhandler._cert_id_lookup()\")\n\n        cert_id = None\n\n        # we misuse header_info_get() to get the tracking id from database\n        cert_recode = b64_url_recode(self.logger, cert_raw)\n        pid_list = header_info_get(\n            self.logger,\n            csr=cert_recode,\n            vlist=[\"poll_identifier\"],\n            field_name=\"cert_raw\",\n        )\n\n        for ele in pid_list:\n            if \"poll_identifier\" in ele:\n                cert_id = ele[\"poll_identifier\"]\n                break\n\n        if not cert_id:\n            # lookup through NCLM API\n            self.logger.info(\"Cert_id not found in database. Lookup trough NCLM API\")\n            cert_id = self._certid_get_from_serial(cert_raw)\n\n        self.logger.debug(\"CAhandler._cert_id_lookup() ended with %s\", cert_id)\n        return cert_id\n\n    def _config_api_access_check(self):\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_api_access_check()\")\n\n        if not self.api_host:\n            self.logger.error('Missing \"api_host\" in configuration.')\n            self.error = \"api_host to be set in config file\"\n\n        if not self.error and not self.credential_dic.get(\"api_user\"):\n            self.logger.error('Missing \"api_user\" in configuration.')\n            self.error = \"api_user to be set in config file\"\n\n        if not self.error and not (\n            \"api_password\" in self.credential_dic\n            and self.credential_dic[\"api_password\"]\n        ):\n            self.logger.error('Missing \"api_password\" in configuration.')\n            self.error = \"api_password to be set in config file\"\n\n        self.logger.debug(\"CAhandler._config_api_access_check() ended\")\n\n    def _config_names_check(self):\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_names_check()\")\n\n        if not self.error and not self.container_info_dic.get(\"name\"):\n            self.logger.error('\"tsg_name\" to be set in config file')\n            self.error = \"tsg_name to be set in config file\"\n\n        if not self.error and not self.ca_name:\n            self.logger.error('\"ca_name\" to be set in config file')\n            self.error = \"ca_name to be set in config file\"\n\n        if not self.error and self.ca_bundle is False:\n            self.logger.warning(\n                'CA bundle validation is disabled (\"ca_bundle\" set to False). Server certificate will not be validated.'\n            )\n\n        self.logger.debug(\"CAhandler._config_names_check() ended\")\n\n    def _config_check(self):\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n\n        self._config_api_access_check()\n        self._config_names_check()\n\n        self.logger.debug(\"CAhandler._config_check() ended\")\n\n    def _config_api_user_load(self, config_dic: Dict[str, str]):\n        \"\"\"load user\"\"\"\n        self.logger.debug(\"CAhandler._config_api_user_load()\")\n\n        if \"api_user_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.credential_dic[\"api_user\"] = os.environ[\n                    config_dic.get(\"CAhandler\", \"api_user_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\"Unable to load API user from environment: %s\", err)\n        if \"api_user\" in config_dic[\"CAhandler\"]:\n            if self.credential_dic[\"api_user\"]:\n                self.logger.info(\"Overwrite api_user\")\n            self.credential_dic[\"api_user\"] = config_dic.get(\"CAhandler\", \"api_user\")\n\n        self.logger.debug(\"CAhandler._config_api_user_load() ended.\")\n\n    def _config_api_password_load(self, config_dic: Dict[str, str]):\n        \"\"\"load password\"\"\"\n        self.logger.debug(\"CAhandler._config_api_password_load()\")\n\n        if \"api_password_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.credential_dic[\"api_password\"] = os.environ[\n                    config_dic.get(\"CAhandler\", \"api_password_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\"Could not load password_variable:%s\", err)\n        if \"api_password\" in config_dic[\"CAhandler\"]:\n            if self.credential_dic[\"api_password\"]:\n                self.logger.info(\"Overwrite api_password\")\n            self.credential_dic[\"api_password\"] = config_dic.get(\n                \"CAhandler\", \"api_password\"\n            )\n\n        self.logger.debug(\"CAhandler._config_api_password_load() ended\")\n\n    def _config_names_load(self, config_dic: Dict[str, str]):\n        \"\"\"load names from config\"\"\"\n        self.logger.debug(\"CAhandler._config_names_load()\")\n\n        self.api_host = config_dic.get(\"CAhandler\", \"api_host\", fallback=self.api_host)\n        self.ca_name = config_dic.get(\"CAhandler\", \"ca_name\", fallback=self.ca_name)\n        self.template_info_dic[\"name\"] = config_dic.get(\n            \"CAhandler\", \"template_name\", fallback=None\n        )\n        if \"container_name\" in config_dic[\"CAhandler\"]:\n            self.container_info_dic[\"name\"] = config_dic.get(\n                \"CAhandler\", \"container_name\", fallback=None\n            )\n        elif \"tsg_name\" in config_dic[\"CAhandler\"]:\n            # for backwards compatibility\n            self.logger.warning(\n                \"Configuration uses deprecated 'tsg_name'. Use 'container_name' instead.\"\n            )\n            self.container_info_dic[\"name\"] = config_dic.get(\n                \"CAhandler\", \"tsg_name\", fallback=None\n            )\n\n        self.logger.debug(\"CAhandler._config_names_load() ended\")\n\n    def _config_proxy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load proxy configuration\"\"\"\n        self.logger.debug(\"CAhandler._config_proxy_load()\")\n\n        if \"DEFAULT\" in config_dic and \"proxy_server_list\" in config_dic[\"DEFAULT\"]:\n            try:\n                proxy_list = json.loads(config_dic.get(\"DEFAULT\", \"proxy_server_list\"))\n                url_dic = parse_url(self.logger, self.api_host)\n                if \"host\" in url_dic:\n                    (fqdn, _port) = url_dic[\"host\"].split(\":\")\n                    proxy_server = proxy_check(self.logger, fqdn, proxy_list)\n                    self.proxy = {\"http\": proxy_server, \"https\": proxy_server}\n            except Exception as err_:\n                self.logger.warning(\n                    \"Failed to load proxy_server_list from configuration: %s\",\n                    err_,\n                )\n        self.logger.debug(\"CAhandler._config_proxy_load() ended\")\n\n    def _config_timer_load(self, config_dic: Dict[str, str]):\n        \"\"\"load timer\"\"\"\n        self.logger.debug(\"CAhandler._config_proxy_load()\")\n\n        # check if we get a ca bundle for verification\n        if \"ca_bundle\" in config_dic[\"CAhandler\"]:\n            try:\n                self.ca_bundle = config_dic.getboolean(\"CAhandler\", \"ca_bundle\")\n            except Exception:\n                self.ca_bundle = config_dic.get(\n                    \"CAhandler\", \"ca_bundle\", fallback=self.ca_bundle\n                )\n\n        if \"request_timeout\" in config_dic[\"CAhandler\"]:\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\n                        \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                    )\n                )\n            except Exception:\n                self.request_timeout = 20\n\n        self.logger.debug(\"CAhandler._config_proxy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=r0912\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n        if \"CAhandler\" in config_dic:\n\n            self._config_names_load(config_dic)\n            self._config_api_user_load(config_dic)\n            self._config_api_password_load(config_dic)\n            self._config_timer_load(config_dic)\n\n        self._config_proxy_load(config_dic)\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _container_id_lookup(self):\n        \"\"\"get target system id based on name\"\"\"\n        self.logger.debug(\n            \"CAhandler._container_id_lookup() for tsg: %s\",\n            self.container_info_dic[\"name\"],\n        )\n        try:\n            tsg_list = requests.get(\n                self.api_host\n                + \"/containers?freeText=\"\n                + str(self.container_info_dic[\"name\"])\n                + \"&offset=0&limit=50&fetchPath=true\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\"Failed to retrieve container id: %s\", err_)\n            tsg_list = []\n\n        if \"items\" in tsg_list:\n            for tsg in tsg_list[\"items\"]:\n                if \"name\" in tsg and \"id\" in tsg:\n                    if self.container_info_dic[\"name\"] == tsg[\"name\"]:\n                        self.container_info_dic[\"id\"] = tsg[\"id\"]\n                        break\n                else:\n                    self.logger.error(\"Incomplete container response: %s\", tsg)\n        else:\n            self.logger.error(\n                \"No target system groups found for filter: %s.\",\n                self.container_info_dic[\"name\"],\n            )\n        self.logger.debug(\n            \"CAhandler._container_id_lookup() ended with: %s\",\n            str(self.container_info_dic[\"id\"]),\n        )\n\n    def _csr_check(self, csr: str) -> str:\n        \"\"\"check csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        # check for eab profiling and header_info\n        error = eab_profile_header_info_check(self.logger, self, csr, \"template_name\")\n\n        self.logger.debug(\"CAhandler._csr_check() ended with: %s\", error)\n        return error\n\n    def _enroll(self, csr: str, ca_id: int) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate from NCLM\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        cert_id = None\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"headers\", \"credential_dic\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        if ca_id and self.container_info_dic[\"id\"]:\n            # enroll operation\n            (error, cert_bundle, cert_raw, cert_id) = self._cert_enroll(csr, ca_id)\n        else:\n            error = f'Enrollment aborted. ca: {ca_id}, tsg_id: {self.container_info_dic[\"id\"]}'\n            self.logger.info(\n                \"CAhandler.eroll(): Enrollment aborted. ca_id: %s, container: %s\",\n                ca_id,\n                self.container_info_dic[\"id\"],\n            )\n\n        self.logger.debug(\"CAhandler._enroll() ended with: %s\", error)\n        return (error, cert_bundle, cert_raw, cert_id)\n\n    def _login(self):\n        \"\"\"_login into NCLM API\"\"\"\n        self.logger.debug(\"CAhandler._login()\")\n        # check first if API is reachable\n        api_response = requests.get(\n            self.api_host + \"/v1\",\n            proxies=self.proxy,\n            timeout=self.request_timeout,\n            verify=self.ca_bundle,\n        )\n        self.logger.debug(\"api response code:%s\", api_response.status_code)\n\n        if api_response.ok:\n            # all fine try to login\n            if \"versionNumber\" in api_response.json():\n                self.nclm_version = api_response.json()[\"versionNumber\"]\n                self.logger.debug(\"NCLM version: %s\", self.nclm_version)\n\n            self.logger.debug(\n                'log in to %s as user \"%s\"',\n                self.api_host,\n                self.credential_dic[\"api_user\"],\n            )\n            data = {\n                \"username\": self.credential_dic[\"api_user\"],\n                \"password\": self.credential_dic[\"api_password\"],\n            }\n            api_response = requests.post(\n                url=self.api_host\n                + self.api_version\n                + \"/token?grant_type=client_credentials\",\n                json=data,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n                verify=self.ca_bundle,\n            )\n\n            if api_response.ok:\n                json_dic = api_response.json()\n                if \"access_token\" in json_dic:\n                    self.headers = {\n                        \"Authorization\": f\"Bearer {json_dic['access_token']}\"\n                    }\n                    _username = json_dic.get(\"username\", None)\n                    _realms = json_dic.get(\"realms\", None)\n                    self.logger.debug(\n                        \"login response:\\n user: %s\\n token: %s\\n realms: %s\\n\",\n                        _username,\n                        json_dic[\"access_token\"],\n                        _realms,\n                    )\n                else:\n                    self.logger.error(\"No token returned after logging in. Aborting.\")\n            else:\n                self.logger.error(\"Login Error: %s\", api_response.status_code)\n        else:\n            # If response code is not ok (200), print the resulting http error code with description\n            self.logger.error(\"Login failed. Error: %s\", api_response.status_code)\n\n    def _revocation_status_poll(\n        self, job_id: int, err_dic: Dict[str, str]\n    ) -> Tuple[int, str, str]:\n        \"\"\"poll status of revocation job\"\"\"\n        self.logger.debug(\"CAhandler._revocation_status_poll()\")\n\n        cnt = 0\n        while cnt < 10:\n            response = requests.get(\n                f\"{self.api_host}{self.api_version}/jobs/{job_id}\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n            if \"status\" in response and response[\"status\"] in [\"done\", \"failed\"]:\n                if response[\"status\"] == \"done\":\n                    code = 200\n                    message = None\n                    detail = None\n                elif response[\"status\"] == \"failed\":\n                    code = 500\n                    message = err_dic[\"serverinternal\"]\n                    detail = \"Revocation operation failed: error from API\"\n                break\n            time.sleep(self.wait_interval)\n            cnt += 1\n\n        if cnt == 10:\n            code = 500\n            message = err_dic[\"serverinternal\"]\n            detail = \"Revocation operation failed: Timeout\"\n\n        self.logger.debug(\"CAhandler._revocation_status_poll() ended with: %s\", code)\n        return (code, message, detail)\n\n    def _template_list_get(self, ca_id: int) -> Dict[str, str]:\n        \"\"\"get list of templates\"\"\"\n        self.logger.debug(\"CAhandler._template_list_get(%s)\", ca_id)\n        try:\n            template_list = requests.get(\n                f\"{self.api_host}{self.api_version}/containers/{self.container_info_dic['id']}/issuers/{ca_id}/templates\",\n                headers=self.headers,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n        except Exception as err_:\n            self.logger.error(\"Failed to retrieve template list: %s\", err_)\n            template_list = []\n\n        if \"items\" in template_list:\n            tmpl_cnt = len(template_list[\"items\"])\n        else:\n            tmpl_cnt = 0\n\n        self.logger.debug(\n            \"CAhandler._template_list_get() ended with: %s templates\", tmpl_cnt\n        )\n        return template_list\n\n    def _templates_enumerate(self, template_list: Dict[str, str]):\n        \"\"\"get template id based on name\"\"\"\n        self.logger.debug(\n            \"CAhandler._templates_enumerate() for template: %s\",\n            self.template_info_dic[\"name\"],\n        )\n\n        for template in template_list[\"items\"]:\n            if (\n                \"name\" in template\n                and template[\"name\"] == self.template_info_dic[\"name\"]\n                and \"id\" in template\n            ):\n                self.template_info_dic[\"id\"] = template[\"id\"]\n                break\n\n    def _template_id_lookup(self, ca_id: int):\n        \"\"\"get template id based on name\"\"\"\n        self.logger.debug(\n            \"CAhandler._template_id_lookup() for template: %s\",\n            self.template_info_dic[\"name\"],\n        )\n\n        # get list of templates\n        template_list = self._template_list_get(ca_id)\n\n        # enumerate templates to get template-id\n        if \"items\" in template_list:\n            self._templates_enumerate(template_list)\n        else:\n            self.logger.error(\n                \"No templates found for filter: %s.\",\n                self.template_info_dic[\"name\"],\n            )\n\n        self.logger.debug(\n            \"CAhandler._template_id_lookup() ended with: %s\",\n            str(self.template_info_dic[\"id\"]),\n        )\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate from NCLM\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        cert_id = None\n\n        # recode csr\n        csr = b64_url_recode(self.logger, csr)\n\n        if not self.error:\n            if self.container_info_dic[\"id\"]:\n\n                # templating\n                ca_id = self._ca_policylink_id_lookup()\n\n                if (\n                    ca_id\n                    and self.template_info_dic[\"name\"]\n                    and not self.template_info_dic[\"id\"]\n                ):\n                    self._template_id_lookup(ca_id)\n\n                error = self._csr_check(csr)\n\n                if not error:\n                    (error, cert_bundle, cert_raw, cert_id) = self._enroll(csr, ca_id)\n                else:\n                    self.logger.error(\n                        \"EAB profile lookup failed with error: %s\",\n                        error,\n                    )\n            else:\n                error = f'ID lookup for container\"{self.container_info_dic[\"name\"]}\" failed.'\n        else:\n            error = self.error\n            self.logger.error(self.error)\n\n        self.logger.debug(\"CAhandler.enroll() ended\")\n        return (error, cert_bundle, cert_raw, cert_id)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        self._config_check()\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", self.error)\n        return self.error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        cert: str,\n        rev_reason: str = \"unspecified\",\n        rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        err_dic = error_dic_get(self.logger)\n\n        code = 500\n        message = err_dic[\"serverinternal\"]\n        detail = \"Revocation operation failed\"\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        # get tracking id as input for revocation call\n        cert_id = self._cert_id_lookup(cert)\n\n        if cert_id:\n            data_dic = {\"reason\": rev_reason, \"time\": rev_date}\n            response = self._api_post(\n                f\"{self.api_host}{self.api_version}/certificates/{cert_id}/revoke\",\n                data_dic,\n            )\n            if \"urls\" in response and \"job\" in response[\"urls\"]:\n                job_id = response[\"urls\"][\"job\"].replace(\"/v2/jobs/\", \"\")\n            else:\n                job_id = None\n                self.logger.error(\n                    \"Job ID lookup failed for certificate: %s\",\n                    cert_id,\n                )\n\n        if job_id:\n            (code, message, detail) = self._revocation_status_poll(job_id, err_dic)\n\n        self.logger.debug(\"CAhandler.revoke() ended with: %s\", code)\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/openssl_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"handler for an openssl ca\"\"\"\nfrom __future__ import print_function\nimport os\nimport datetime\nimport json\nfrom typing import List, Tuple, Dict\nimport base64\nimport uuid\nimport re\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization, hashes\nfrom cryptography.x509 import (\n    BasicConstraints,\n    ExtendedKeyUsage,\n    SubjectKeyIdentifier,\n    AuthorityKeyIdentifier,\n    KeyUsage,\n    SubjectAlternativeName,\n)\nfrom cryptography.x509.oid import ExtendedKeyUsageOID, NameOID\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    load_config,\n    build_pem_file,\n    uts_now,\n    uts_to_date_utc,\n    b64_url_recode,\n    cert_serial_get,\n    convert_string_to_byte,\n    convert_byte_to_string,\n    csr_cn_get,\n    csr_san_get,\n)\n\nBLOCK_ALL_DOMAIN = \"block.all\"\n\n\nclass CAhandler(object):\n    \"\"\"CA  handler\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None):\n        self.debug = debug\n        self.logger = logger\n        self.issuer_dict = {\n            \"issuing_ca_key\": None,\n            \"issuing_ca_cert\": None,\n            \"issuing_ca_crl\": None,\n        }\n        self.ca_cert_chain_list = []\n        self.cert_validity_days = 365\n        self.cert_validity_adjust = False\n        self.openssl_conf = None\n        self.cert_save_path = None\n        self.save_cert_as_hex = False\n        self.allowed_domainlist = []\n        self.blocked_domainlist = []\n        self.cn_enforce = False\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        if not self.issuer_dict[\"issuing_ca_key\"]:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _ca_load(self) -> Tuple[object, object]:\n        \"\"\"load ca key and cert\"\"\"\n        self.logger.debug(\"CAhandler._ca_load()\")\n        ca_key = None\n        ca_cert = None\n        # open key and cert\n        if \"issuing_ca_key\" in self.issuer_dict:\n            if os.path.exists(self.issuer_dict[\"issuing_ca_key\"]):\n                with open(self.issuer_dict[\"issuing_ca_key\"], \"rb\") as fso:\n                    ca_key = serialization.load_pem_private_key(\n                        fso.read(),\n                        password=self.issuer_dict.get(\"passphrase\", None),\n                        backend=default_backend(),\n                    )\n        if \"issuing_ca_cert\" in self.issuer_dict:\n            if os.path.exists(self.issuer_dict[\"issuing_ca_cert\"]):\n                with open(self.issuer_dict[\"issuing_ca_cert\"], \"rb\") as fso:\n                    ca_cert = x509.load_pem_x509_certificate(\n                        fso.read(), backend=default_backend()\n                    )\n        self.logger.debug(\"CAhandler._ca_load() ended\")\n        return (ca_key, ca_cert)\n\n    def _cert_extension_ku_parse(self, ext: str) -> Dict[str, str]:\n        self.logger.debug(\"CAhandler._cert_extension_ku_parse()\")\n\n        template_dic = {\n            \"digital_signature\": False,\n            \"content_commitment\": False,\n            \"key_encipherment\": False,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        ku_mapping_dic = {\n            \"digitalsignature\": \"digital_signature\",\n            \"nonrepudiation\": \"content_commitment\",\n            \"keyencipherment\": \"key_encipherment\",\n            \"dataencipherment\": \"data_encipherment\",\n            \"keyagreement\": \"key_agreement\",\n            \"keycertsign\": \"key_cert_sign\",\n            \"crlsign\": \"crl_sign\",\n            \"encipheronly\": \"encipher_only\",\n            \"decipheronly\": \"decipher_only\",\n        }\n        for attribute in ext.split(\",\"):\n            if attribute.strip().lower() in ku_mapping_dic:\n                self.logger.debug(\n                    \"CAhandler._cert_extension_ku_parse(): found %s\", attribute\n                )\n                template_dic[ku_mapping_dic[attribute.strip().lower()]] = True\n\n        self.logger.debug(\"CAhandler._cert_extension_ku_parse() ended\")\n        return template_dic\n\n    def _cert_extension_eku_parse(self, ext: str) -> List[str]:\n        self.logger.debug(\"CAhandler._cert_extension_eku_parse()\")\n\n        # eku included in tempalate\n        eku_mapping_dic = {\n            \"clientauth\": ExtendedKeyUsageOID.CLIENT_AUTH,\n            \"serverauth\": ExtendedKeyUsageOID.SERVER_AUTH,\n            \"codesigning\": ExtendedKeyUsageOID.CODE_SIGNING,\n            \"emailprotection\": ExtendedKeyUsageOID.EMAIL_PROTECTION,\n            \"timestamping\": ExtendedKeyUsageOID.TIME_STAMPING,\n            \"ocspsigning\": ExtendedKeyUsageOID.OCSP_SIGNING,\n            \"ekeyuse\": \"eKeyUse\",  # this is just for testing\n        }\n\n        # backwards compatibility with cryptography module coming Ubuntu 22.04\n        if hasattr(ExtendedKeyUsageOID, \"KERBEROS_PKINIT_KDC\"):\n            eku_mapping_dic[\"pkInitKDC\"] = ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC\n\n        eku_list = []\n        for attribute in ext.split(\",\"):\n            if attribute.strip().lower() in eku_mapping_dic:\n                self.logger.debug(\n                    \"CAhandler._cert_extension_eku_parse(): found %s\", attribute\n                )\n                eku_list.append(eku_mapping_dic[attribute.strip().lower()])\n\n        self.logger.debug(\"CAhandler._cert_extension_eku_parse() ended\")\n        return eku_list\n\n    def _cert_extension_dic_parse(\n        self, cert_extension_dic: Dict[str, str], cert: str, ca_cert: str\n    ) -> List[object]:\n        \"\"\"parse certificate exteions loaded from config file\"\"\"\n        self.logger.debug(\"CAhandler.cert_extesion_dic_parse()\")\n\n        extension_list = []\n        for ext_name, ext in cert_extension_dic.items():\n            _tmp_dic = {\"critical\": ext[\"critical\"]}\n\n            if ext_name.lower() == \"basicconstraints\":\n                self.logger.debug(\n                    \"CAhandler.cert_extesion_dic_parse(): basicConstraints\"\n                )\n                _tmp_dic[\"name\"] = BasicConstraints(ca=False, path_length=None)\n            elif ext_name.lower() == \"subjectkeyidentifier\":\n                self.logger.debug(\n                    \"CAhandler.cert_extesion_dic_parse(): subjectKeyIdentifier\"\n                )\n                _tmp_dic[\"name\"] = SubjectKeyIdentifier.from_public_key(\n                    cert.public_key()\n                )\n                _tmp_dic[\"critical\"] = False\n            elif ext_name.lower() == \"authoritykeyidentifier\":\n                self.logger.debug(\n                    \"CAhandler.cert_extesion_dic_parse(): authorityKeyIdentifier\"\n                )\n                _tmp_dic[\"name\"] = AuthorityKeyIdentifier.from_issuer_public_key(\n                    ca_cert.public_key()\n                )\n            elif ext_name.lower() == \"keyusage\":\n                self.logger.debug(\"CAhandler.cert_extesion_dic_parse(): keyUsage\")\n                _tmp_dic[\"name\"] = KeyUsage(\n                    **self._cert_extension_ku_parse(ext[\"value\"])\n                )\n            elif ext_name.lower() == \"extendedkeyusage\":\n                self.logger.debug(\n                    \"CAhandler.cert_extesion_dic_parse(): extendedKeyUsage\"\n                )\n                _tmp_dic[\"name\"] = ExtendedKeyUsage(\n                    self._cert_extension_eku_parse(ext[\"value\"])\n                )\n\n            extension_list.append(_tmp_dic)\n\n        self.logger.debug(\"CAhandler.cert_extesion_dic_parse() ended.\")\n        return extension_list\n\n    def _certificate_extensions_load(self) -> Dict[str, str]:\n        \"\"\"verify certificate chain\"\"\"\n        self.logger.debug(\"CAhandler._certificate_extensions_load()\")\n\n        file_dic = dict(load_config(self.logger, cfg_file=self.openssl_conf))\n\n        cert_extention_dic = {}\n        if \"extensions\" in file_dic:\n            for extension in file_dic[\"extensions\"]:\n\n                cert_extention_dic[extension] = {}\n                parameters = file_dic[\"extensions\"][extension].split(\",\")\n\n                # set crititcal task if applicable\n                if parameters[0] == \"critical\":\n                    cert_extention_dic[extension][\"critical\"] = bool(parameters.pop(0))\n                else:\n                    cert_extention_dic[extension][\"critical\"] = False\n\n                # remove leading blank from first element\n                parameters[0] = parameters[0].lstrip()\n\n                # check if we have an issuer option (if so remove it and mark it as to be set)\n                if \"issuer:\" in parameters[-1]:\n                    cert_extention_dic[extension][\"issuer\"] = bool(parameters.pop(-1))\n\n                # check if we have an issuer option (if so remove it and mark it as to be set)\n                if \"subject:\" in parameters[-1]:\n                    cert_extention_dic[extension][\"subject\"] = bool(parameters.pop(-1))\n\n                # combine the remaining items and put them in as values\n                cert_extention_dic[extension][\"value\"] = \",\".join(parameters)\n\n        self.logger.debug(\"CAhandler._certificate_extensions_load() ended\")\n        return cert_extention_dic\n\n    def _certificate_store(self, cert: object):\n        \"\"\"store certificate on disk\"\"\"\n        self.logger.debug(\"CAhandler._certificate_store()\")\n        serial = cert.serial_number\n\n        # save cert if needed\n        if self.cert_save_path and self.cert_save_path is not None:\n            # create cert-store dir if not existing\n            if not os.path.isdir(self.cert_save_path):\n                self.logger.debug(\"create certsavedir %s\", self.cert_save_path)\n                os.mkdir(self.cert_save_path)\n\n            # determine filename\n            if self.save_cert_as_hex:\n                self.logger.debug(\n                    \"Convert serial to hex: %s: %s\", serial, f\"{serial:X}\"\n                )\n                cert_file = f\"{serial:X}\"\n            else:\n                cert_file = str(serial)\n            with open(f\"{self.cert_save_path}/{cert_file}.pem\", \"wb\") as fso:\n                fso.write(cert.public_bytes(serialization.Encoding.PEM))\n        else:\n            self.logger.error(\n                \"Certificate storage failed: cert_save_path is missing in the handler configuration.\"\n            )\n\n        self.logger.debug(\"CAhandler._certificate_store() ended\")\n\n    def _config_check_issuer(self) -> str:\n        \"\"\"check issuing CA configuration\"\"\"\n        self.logger.debug(\"CAhandler._config_check_issuer()\")\n\n        error = None\n        if \"issuing_ca_key\" in self.issuer_dict and self.issuer_dict[\"issuing_ca_key\"]:\n            if not os.path.exists(self.issuer_dict[\"issuing_ca_key\"]):\n                error = f\"issuing_ca_key {self.issuer_dict['issuing_ca_key']} does not exist\"\n        else:\n            error = \"issuing_ca_key not specfied in config_file\"\n\n        if not error:\n            if (\n                \"issuing_ca_cert\" in self.issuer_dict\n                and self.issuer_dict[\"issuing_ca_cert\"]\n            ):\n                if not os.path.exists(self.issuer_dict[\"issuing_ca_cert\"]):\n                    error = f\"issuing_ca_cert {self.issuer_dict['issuing_ca_cert']} does not exist\"\n            else:\n                error = \"issuing_ca_cert must be specified in config file\"\n\n        self.logger.debug(\"CAhandler._config_check_issuer() ended with: %s\", error)\n        return error\n\n    def _config_check_crl(self, error: str = None) -> str:\n        \"\"\"check crl config\"\"\"\n        self.logger.debug(\"CAhandler._config_check_crl()\")\n\n        if not error:\n            if (\n                \"issuing_ca_crl\" in self.issuer_dict\n                and self.issuer_dict[\"issuing_ca_crl\"]\n            ):\n                if not os.path.exists(self.issuer_dict[\"issuing_ca_crl\"]):\n                    self.logger.info(\n                        \"Issuing_ca_crl %s does not exist.\",\n                        self.issuer_dict[\"issuing_ca_crl\"],\n                    )\n            else:\n                error = \"issuing_ca_crl must be specified in config file\"\n\n        self.logger.debug(\"CAhandler._config_check_crl() ended with: %s\", error)\n        return error\n\n    def _config_parameters_check(self, error: str = None) -> str:\n        \"\"\"check remaining configuration\"\"\"\n        self.logger.debug(\"CAhandler._config_parameters_check()\")\n\n        if not error:\n            if self.cert_save_path:\n                if not os.path.exists(self.cert_save_path):\n                    error = f\"cert_save_path {self.cert_save_path} does not exist\"\n            else:\n                error = \"cert_save_path must be specified in config file\"\n\n        if not error and self.openssl_conf and not os.path.exists(self.openssl_conf):\n            error = f\"openssl_conf {self.openssl_conf} does not exist\"\n\n        self.logger.debug(\"CAhandler._config_parameters_check() ended with: %s\", error)\n        return error\n\n    def _config_check(self) -> str:\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n\n        # run checks\n        error = self._config_check_issuer()\n        error = self._config_check_crl(error)\n        error = self._config_parameters_check(error)\n\n        if error:\n            self.logger.error(\"Configuration error: %s\", error)\n\n        self.logger.debug(\"CAhandler._config_check() ended\")\n        return error\n\n    def _config_domainlists_load(self, config_dic: Dict[str, str]):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_domainlists_load()\")\n\n        self.openssl_conf = config_dic.get(\n            \"CAhandler\", \"openssl_conf\", fallback=self.openssl_conf\n        )\n        if \"allowed_domainlist\" in config_dic[\"CAhandler\"]:\n            try:\n                self.allowed_domainlist = json.loads(\n                    config_dic.get(\"CAhandler\", \"allowed_domainlist\")\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load allowed_domainlist parameter. Block all domains: %s\",\n                    err,\n                )\n                self.allowed_domainlist = [BLOCK_ALL_DOMAIN]\n\n        if \"blocked_domainlist\" in config_dic[\"CAhandler\"]:\n            try:\n                self.blocked_domainlist = json.loads(\n                    config_dic.get(\"CAhandler\", \"blocked_domainlist\")\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load blocked_domainlist parameter. Block all domains: %s\",\n                    err,\n                )\n                self.allowed_domainlist = [BLOCK_ALL_DOMAIN]\n\n        if \"whitelist\" in config_dic[\"CAhandler\"]:\n            self.logger.error(\n                'Deprecated config: found \"whitelist\". Please rename to \"allowed_domainlist\".'\n            )\n            try:\n                self.allowed_domainlist = json.loads(\n                    config_dic.get(\"CAhandler\", \"whitelist\")\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load whitelist parameter. Block all domains: %s\", err\n                )\n                self.allowed_domainlist = [BLOCK_ALL_DOMAIN]\n\n        if \"blacklist\" in config_dic[\"CAhandler\"]:\n            self.logger.error(\n                'Deprecated config: found \"blacklist\". Please rename to \"blocked_domainlist\".'\n            )\n            try:\n                self.blocked_domainlist = json.loads(\n                    config_dic.get(\"CAhandler\", \"blacklist\")\n                )\n\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load blacklist parameter. Block all domains: %s\", err\n                )\n                self.allowed_domainlist = [BLOCK_ALL_DOMAIN]\n\n        self.logger.debug(\"CAhandler._config_domainlists_load() ended\")\n\n    def _config_credentials_load(self, config_dic: Dict[str, str]):\n        \"\"\"load credential config\"\"\"\n        self.logger.debug(\"CAhandler._config_credentials_load()\")\n\n        self.issuer_dict[\"issuing_ca_key\"] = config_dic.get(\n            \"CAhandler\", \"issuing_ca_key\", fallback=None\n        )\n        self.issuer_dict[\"issuing_ca_cert\"] = config_dic.get(\n            \"CAhandler\", \"issuing_ca_cert\", fallback=None\n        )\n\n        if \"issuing_ca_key_passphrase_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.issuer_dict[\"passphrase\"] = os.environ[\n                    config_dic.get(\"CAhandler\", \"issuing_ca_key_passphrase_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\n                    \"Unable to load issuing_ca_key_passphrase_variable from environment: %s\",\n                    err,\n                )\n        if \"issuing_ca_key_passphrase\" in config_dic[\"CAhandler\"]:\n            if \"passphrase\" in self.issuer_dict and self.issuer_dict[\"passphrase\"]:\n                self.logger.info(\"Overwrite issuing_ca_key_passphrase_variable\")\n            self.issuer_dict[\"passphrase\"] = config_dic.get(\n                \"CAhandler\", \"issuing_ca_key_passphrase\"\n            )\n\n        # convert passphrase\n        if \"passphrase\" in self.issuer_dict:\n            self.issuer_dict[\"passphrase\"] = self.issuer_dict[\"passphrase\"].encode(\n                \"ascii\"\n            )\n\n        self.logger.debug(\"CAhandler._config_credentials_load() ended\")\n\n    def _config_policy_load(self, config_dic: Dict[str, str]):\n        \"\"\"load certificate policy\"\"\"\n        self.logger.debug(\"CAhandler._config_policy_load()\")\n\n        self.cert_save_path = config_dic.get(\n            \"CAhandler\", \"cert_save_path\", fallback=self.cert_save_path\n        )\n        self.issuer_dict[\"issuing_ca_crl\"] = config_dic.get(\n            \"CAhandler\", \"issuing_ca_crl\", fallback=None\n        )\n\n        if \"ca_cert_chain_list\" in config_dic[\"CAhandler\"]:\n            try:\n                self.ca_cert_chain_list = json.loads(\n                    config_dic[\"CAhandler\"][\"ca_cert_chain_list\"]\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load ca_cert_chain_list parameter from config file: %s\",\n                    err,\n                )\n                self.ca_cert_chain = []\n\n        if \"cert_validity_days\" in config_dic[\"CAhandler\"]:\n            self.cert_validity_days = int(config_dic[\"CAhandler\"][\"cert_validity_days\"])\n        try:\n            self.cn_enforce = config_dic.getboolean(\n                \"CAhandler\", \"cn_enforce\", fallback=False\n            )\n        except Exception:\n            self.logger.error(\"Could not parse cn_enforce from config file.\")\n        try:\n            self.cert_validity_adjust = config_dic.getboolean(\n                \"CAhandler\", \"cert_validity_adjust\", fallback=False\n            )\n        except Exception:\n            self.logger.error(\"Could not parse cert_validity_adjust from config file.\")\n\n        self.logger.debug(\"CAhandler._config_policy_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        # load credentials\n        self._config_credentials_load(config_dic)\n\n        # load policy options\n        self._config_policy_load(config_dic)\n\n        # load allow/block lists\n        self._config_domainlists_load(config_dic)\n\n        self.save_cert_as_hex = config_dic.getboolean(\n            \"CAhandler\", \"save_cert_as_hex\", fallback=False\n        )\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _chk_san_lists_get(self, csr: str) -> Tuple[List[str], List[bool]]:\n        \"\"\"check lists\"\"\"\n        self.logger.debug(\"CAhandler._chk_san_lists_get()\")\n\n        # get sans and build a list\n        _san_list = csr_san_get(self.logger, csr)\n\n        check_list = []\n        san_list = []\n\n        if _san_list:\n            for san in _san_list:\n                try:\n                    # SAN list must be modified/filtered)\n                    (_san_type, san_value) = san.lower().split(\":\")\n                    san_list.append(san_value)\n                except Exception:\n                    # force check to fail as something went wrong during parsing\n                    check_list.append(False)\n                    self.logger.debug(\n                        \"CAhandler._csr_check(): san_list parsing failed at entry: %s\",\n                        san,\n                    )\n\n        self.logger.debug(\"CAhandler._chk_san_lists_get() ended\")\n        return (san_list, check_list)\n\n    def _cn_add(self, csr: str, san_list: List[str]) -> Tuple[List[str], str]:\n        \"\"\"add CN if required\"\"\"\n        self.logger.debug(\"CAhandler._cn_add()\")\n\n        # get common name and attach it to san_list\n        cn_ = csr_cn_get(self.logger, csr)\n\n        if not cn_ and san_list:\n            enforced_cn = san_list[0]\n            self.logger.info(\"Enforce CN to %s\", enforced_cn)\n        else:\n            enforced_cn = None\n\n        if cn_:\n            cn_ = cn_.lower()\n            if cn_ not in san_list:\n                # append cn to san_list\n                self.logger.debug(\"Ahandler._csr_check(): append cn to san_list\")\n                san_list.append(cn_)\n\n        self.logger.debug(\"CAhandler._cn_add() ended with: %s\", enforced_cn)\n        return (san_list, enforced_cn)\n\n    def _csr_check(self, csr: str) -> Tuple[bool, str]:\n        \"\"\"check CSR against definied allowed_domainlists\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        (san_list, check_list) = self._chk_san_lists_get(csr)\n        (san_list, enforced_cn) = self._cn_add(csr, san_list)\n\n        if self.allowed_domainlist or self.blocked_domainlist:\n            result = False\n\n            # go over the san list and check each entry\n            for san in san_list:\n                check_list.append(\n                    self._string_wlbl_check(\n                        san, self.allowed_domainlist, self.blocked_domainlist\n                    )\n                )\n\n            if check_list:\n                # cover a cornercase with empty checklist (no san, no cn)\n                if False in check_list:\n                    result = False\n                else:\n                    result = True\n        else:\n            result = True\n\n        self.logger.debug(\n            \"CAhandler._csr_check() ended with: %s enforce_cn: %s\", result, enforced_cn\n        )\n        return (result, enforced_cn)\n\n    def _list_regex_check(self, entry: str, list_: List[str]) -> bool:\n        \"\"\"check entry against regex\"\"\"\n        self.logger.debug(\"CAhandler._list_regex_check()\")\n\n        check_result = False\n        for regex in list_:\n            if regex.startswith(\"*.\"):\n                regex = regex.replace(\"*.\", \".\")\n            regex_compiled = re.compile(regex)\n            if bool(regex_compiled.search(entry)):\n                # parameter is in set flag accordingly and stop loop\n                check_result = True\n\n        self.logger.debug(\"CAhandler._list_regex_check() ended with: %s\", check_result)\n        return check_result\n\n    def _list_check(self, entry: str, list_: List[str], toggle: bool = False) -> bool:\n        \"\"\"check string against list\"\"\"\n        self.logger.debug(\"CAhandler._list_check(%s:%s)\", entry, toggle)\n        self.logger.debug(\"check against list: %s\", str(list_))\n\n        # default setting\n        check_result = False\n\n        if entry:\n            if list_:\n                check_result = self._list_regex_check(entry, list_)\n            else:\n                # empty list, flip parameter to make the check successful\n                check_result = True\n\n        if toggle:\n            # toggle result if this is a blocked_domainlist\n            check_result = not check_result\n\n        self.logger.debug(\"CAhandler._list_check() ended with: %s\", check_result)\n        return check_result\n\n    def _pemcertchain_generate(self, ee_cert: str, issuer_cert: str) -> str:\n        \"\"\"build pem chain\"\"\"\n        self.logger.debug(\"CAhandler._pemcertchain_generate()\")\n\n        if issuer_cert:\n            pem_chain = f\"{ee_cert}{issuer_cert}\"\n        else:\n            pem_chain = ee_cert\n\n        for cert in self.ca_cert_chain_list:\n            if os.path.exists(cert):\n                with open(cert, \"r\", encoding=\"utf8\") as fso:\n                    cert_pem = fso.read()\n                pem_chain = f\"{pem_chain}{cert_pem}\"\n\n        self.logger.debug(\"CAhandler._pemcertchain_generate() ended\")\n        return pem_chain\n\n    def _string_wlbl_check(\n        self, entry: str, white_list: List[str], black_list: List[str]\n    ) -> bool:\n        \"\"\"check single against allowed_domainlist and blocked_domainlist\"\"\"\n        self.logger.debug(\"CAhandler._string_wlbl_check(%s)\", entry)\n\n        # default setting\n        chk_result = False\n\n        # check if entry is in white_list\n        wl_check = self._list_check(entry, white_list)\n        if wl_check:\n            self.logger.debug(\"%s in white_list\", entry)\n            if black_list:\n                # we need to check blocked_domainlist if there is a blocked_domainlist and wl check passed\n                if self._list_check(entry, black_list):\n                    self.logger.debug(\"%s in black_list\", entry)\n                else:\n                    self.logger.debug(\"%s not in black_list\", entry)\n                    chk_result = True\n            else:\n                chk_result = wl_check\n        else:\n            self.logger.debug(\"%s not in white_list\", entry)\n\n        self.logger.debug(\n            \"CAhandler._string_wlbl_check(%s) ended with: %s\", entry, chk_result\n        )\n        return chk_result\n\n    def _cert_expiry_get(self, cert):\n        \"\"\"get expiry date of certificate\"\"\"\n        self.logger.debug(\"CAhandler._cert_expiry_get()\")\n\n        expiry_date = cert.not_valid_after\n\n        self.logger.debug(\"CAhandler._cert_expiry_get() ended\")\n        return expiry_date\n\n    def _cacert_expiry_get(self):\n        \"\"\"get closesd expiry date of issuing CA\"\"\"\n        self.logger.debug(\"CAhandler._cacert_expiry_get()\")\n\n        ca_list = self.ca_cert_chain_list\n        if (\n            self.issuer_dict[\"issuing_ca_cert\"]\n            and self.issuer_dict[\"issuing_ca_cert\"] not in ca_list\n        ):\n            ca_list.append(self.issuer_dict[\"issuing_ca_cert\"])\n\n        expiry_days = 0\n        cert = None\n\n        for ca_cert in ca_list:\n            if ca_cert:\n                if os.path.exists(ca_cert):\n                    with open(ca_cert, \"rb\") as fso:\n                        ca_cert = x509.load_pem_x509_certificate(\n                            fso.read(), backend=default_backend()\n                        )\n                        _tmp_expiry_days = (\n                            self._cert_expiry_get(ca_cert) - datetime.datetime.now()\n                        ).days\n                        if not expiry_days or _tmp_expiry_days < expiry_days:\n                            self.logger.debug(\n                                \"CAhandler._cacert_expiry_get(): set expiry_days to %s\",\n                                _tmp_expiry_days,\n                            )\n                            expiry_days = _tmp_expiry_days\n                            cert = ca_cert\n                else:\n                    self.logger.error(\n                        \"CA file %s does not exist\",\n                        ca_cert,\n                    )\n\n        self.logger.debug(\"CAhandler._cacert_expiry_get() ended\")\n        return expiry_days, cert\n\n    def _certexpiry_date_default(self) -> datetime.datetime:\n        \"\"\"set certificate validity\"\"\"\n        self.logger.debug(\"CAhandler._certexpiry_date_default()\")\n\n        # default cert validity is taken from config\n        cert_validity = datetime.datetime.now(\n            datetime.timezone.utc\n        ) + datetime.timedelta(days=self.cert_validity_days)\n\n        self.logger.debug(\"CAhandler._certexpiry_date_default() ended\")\n        return cert_validity\n\n    def _certexpiry_date_set(self) -> datetime.datetime:\n        \"\"\"set certificate validity\"\"\"\n        self.logger.debug(\"CAhandler._certexpiry_date_set()\")\n\n        # default cert validity is taken from config\n        cert_validity = self._certexpiry_date_default()\n\n        if self.cert_validity_adjust:\n            # adjust validity to match the validity of the issuing CA\n\n            (ca_cert_validity, cert) = self._cacert_expiry_get()\n            if ca_cert_validity < self.cert_validity_days:\n                self.logger.info(\n                    \"Adjust validity to %s days.\",\n                    ca_cert_validity,\n                )\n                cert_validity = cert.not_valid_after\n\n        self.logger.debug(\"CAhandler._certexpiry_date_set() ended\")\n        return cert_validity\n\n    def _cert_signing_prep(self, ca_cert: object, req: object, subject: str) -> object:\n        \"\"\"enroll certificate\"\"\"\n        # pylint: disable=R0914, R0915\n        self.logger.debug(\"CAhandler._cert_signing_prep()\")\n\n        cert_validity = self._certexpiry_date_set()\n\n        # sign csr\n        builder = x509.CertificateBuilder()\n        builder = builder.not_valid_before(datetime.datetime.now(datetime.timezone.utc))\n        builder = builder.not_valid_after(cert_validity)\n        builder = builder.issuer_name(ca_cert.subject)\n        builder = builder.subject_name(subject)\n        builder = builder.serial_number(uuid.uuid4().int)\n        builder = builder.public_key(req.public_key())\n\n        self.logger.debug(\"CAhandler._cert_signing_prep() ended\")\n        return builder\n\n    def _cert_extension_default(self, ca_cert: object, req: object) -> List[str]:\n        \"\"\"add default extensions\"\"\"\n        self.logger.debug(\"CAhandler._cert_extension_default()\")\n\n        default_extension_list = [\n            {\"name\": BasicConstraints(ca=False, path_length=None), \"critical\": True},\n            {\n                \"name\": ExtendedKeyUsage(\n                    [ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CLIENT_AUTH]\n                ),\n                \"critical\": False,\n            },\n            {\n                \"name\": KeyUsage(\n                    digital_signature=True,\n                    content_commitment=False,\n                    key_encipherment=True,\n                    data_encipherment=False,\n                    key_agreement=False,\n                    key_cert_sign=False,\n                    crl_sign=False,\n                    encipher_only=False,\n                    decipher_only=False,\n                ),\n                \"critical\": True,\n            },\n        ]\n        if req:\n            default_extension_list.append(\n                {\n                    \"name\": SubjectKeyIdentifier.from_public_key(req.public_key()),\n                    \"critical\": False,\n                },\n            )\n        if ca_cert:\n            default_extension_list.append(\n                {\n                    \"name\": AuthorityKeyIdentifier.from_issuer_public_key(\n                        ca_cert.public_key()\n                    ),\n                    \"critical\": False,\n                }\n            )\n\n        self.logger.debug(\"CAhandler._cert_extension_default() ended\")\n        return default_extension_list\n\n    def _cert_extension_apply(\n        self, builder: object, ca_cert: object, req: object\n    ) -> object:\n        \"\"\"add cert extensions\"\"\"\n        self.logger.debug(\"CAhandler._cert_extension_apply()\")\n\n        # load certificate_profile (if applicable)\n        if self.openssl_conf:\n            cert_extension_dic = self._certificate_extensions_load()\n            extension_list = self._cert_extension_dic_parse(\n                cert_extension_dic, req, ca_cert\n            )\n        else:\n            extension_list = self._cert_extension_default(ca_cert, req)\n\n        # add subject alternative names\n        if req:\n            for ext in req.extensions:\n                if ext.oid._name == \"subjectAltName\":  # pylint: disable=W0212\n                    extension_list.append(\n                        {\"name\": SubjectAlternativeName(ext.value), \"critical\": False}\n                    )\n\n        for extension in extension_list:\n            # add extensions to csr\n            builder = builder.add_extension(\n                extension[\"name\"], critical=extension[\"critical\"]\n            )\n\n        self.logger.debug(\"CAhandler._cert_extension_apply() ended\")\n        return builder\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        # pylint: disable=R0914, R0915\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        cert_raw = None\n\n        error = self._config_check()\n\n        if not error:\n            try:\n                # check CN and SAN against black/whitlist\n                (result, enforce_cn) = self._csr_check(csr)\n\n                if result:\n                    # prepare the CSR\n                    csr = build_pem_file(\n                        self.logger, None, b64_url_recode(self.logger, csr), None, True\n                    )\n\n                    # load ca cert and key\n                    (ca_key, ca_cert) = self._ca_load()\n\n                    # creating a rest from CSR\n                    req = x509.load_pem_x509_csr(\n                        convert_string_to_byte(csr), default_backend()\n                    )\n                    subject = req.subject\n\n                    if self.cn_enforce and enforce_cn:\n                        self.logger.info(\"Overwrite CN with %s\", enforce_cn)\n                        subject = x509.Name(\n                            [x509.NameAttribute(NameOID.COMMON_NAME, enforce_cn)]\n                        )\n\n                    builder = self._cert_signing_prep(ca_cert, req, subject)\n                    builder = self._cert_extension_apply(builder, ca_cert, req)\n\n                    # sign certificate\n                    cert = builder.sign(\n                        private_key=ca_key,\n                        algorithm=hashes.SHA256(),\n                        backend=default_backend(),\n                    )\n\n                    # store certifiate\n                    self._certificate_store(cert)\n                    # create bundle and raw cert\n                    with open(\n                        self.issuer_dict[\"issuing_ca_cert\"], \"r\", encoding=\"utf8\"\n                    ) as ca_fso:\n                        cert_bundle = self._pemcertchain_generate(\n                            convert_byte_to_string(\n                                cert.public_bytes(serialization.Encoding.PEM)\n                            ),\n                            ca_fso.read(),\n                        )\n                        cert_raw = convert_byte_to_string(\n                            base64.b64encode(\n                                cert.public_bytes(serialization.Encoding.DER)\n                            )\n                        )\n                else:\n                    error = \"urn:ietf:params:acme:badCSR\"\n\n            except Exception as err:\n                self.logger.error(\n                    \"Certificate enrollment failed due to exception: %s\", err\n                )\n                error = \"Unknown exception\"\n\n        self.logger.debug(\"CAhandler.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def _crlobject_build(\n        self, ca_cert: object, serial: int\n    ) -> Tuple[x509.CertificateRevocationListBuilder, object]:\n        self.logger.debug(\"CAhandler._crlobject_build()\")\n\n        if os.path.exists(self.issuer_dict[\"issuing_ca_crl\"]):\n            self.logger.info(\n                \"Load existing crl %s)\",\n                self.issuer_dict[\"issuing_ca_crl\"],\n            )\n            # load  existing CRL\n            with open(self.issuer_dict[\"issuing_ca_crl\"], \"rb\") as fso:\n                crl_data = fso.read()\n                crl = x509.load_pem_x509_crl(crl_data, default_backend())\n            builder = x509.CertificateRevocationListBuilder()\n            builder = builder.issuer_name(crl.issuer)\n            # add crl certificates from file to the new crl object\n            for revserial in crl:\n                builder = builder.add_revoked_certificate(revserial)  # pragma: no cover\n\n            # see if the cert to be revokek already in the list\n            ret = crl.get_revoked_certificate_by_serial_number(serial)\n\n        else:\n            self.logger.info(\n                \"Create new crl %s)\",\n                self.issuer_dict[\"issuing_ca_crl\"],\n            )\n            builder = x509.CertificateRevocationListBuilder()\n            builder = builder.issuer_name(ca_cert.issuer)\n            ret = None\n\n        self.logger.debug(\"CAhandler._crlobject_build() ended\")\n        return (builder, ret)\n\n    def revoke(\n        self, cert_pem: str, rev_reason: str = \"unspecified\", rev_date: str = None\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke(%s: %s)\", rev_reason, rev_date)\n        code = None\n        message = None\n        detail = None\n\n        rev_date_format = \"%y%m%d%H%M%SZ\"\n\n        # overwrite revocation date - we ignore what has been submitted\n        rev_date = uts_to_date_utc(uts_now(), rev_date_format)\n\n        if \"issuing_ca_crl\" in self.issuer_dict and self.issuer_dict[\"issuing_ca_crl\"]:\n            # load ca cert and key\n            (ca_key, ca_cert) = self._ca_load()\n\n            # turn of chain_check due to issues in pyopenssl (check is not working if key-usage is set)\n            # result = self._certificate_chain_verify(cert, ca_cert)\n\n            # get serial number from certicate to be revoked\n            serial = cert_serial_get(self.logger, cert_pem)\n\n            if ca_key and ca_cert and serial:\n\n                # build crl object\n                (builder, ret) = self._crlobject_build(ca_cert, serial)\n\n                if not isinstance(ret, x509.RevokedCertificate):\n                    # this is the revocation operation\n                    # Set up the revoked entry\n                    revoked_entry = (\n                        x509.RevokedCertificateBuilder()\n                        .serial_number(serial)\n                        .revocation_date(\n                            datetime.datetime.strptime(rev_date, rev_date_format)\n                        )\n                        .build(default_backend())\n                    )\n                    builder = builder.add_revoked_certificate(revoked_entry)\n\n                    # Sign the CRL\n                    crl = (\n                        builder.last_update(\n                            datetime.datetime.strptime(rev_date, rev_date_format)\n                        )\n                        .next_update(\n                            datetime.datetime.strptime(rev_date, rev_date_format)\n                        )\n                        .sign(ca_key, hashes.SHA256())\n                    )\n\n                    # Save CRL\n                    with open(self.issuer_dict[\"issuing_ca_crl\"], \"wb\") as fso:\n                        fso.write(crl.public_bytes(serialization.Encoding.PEM))\n                    code = 200\n                else:\n                    code = 400\n                    message = \"urn:ietf:params:acme:error:alreadyRevoked\"\n                    detail = \"Certificate has already been revoked\"\n            else:\n                code = 400\n                message = \"urn:ietf:params:acme:error:serverInternal\"\n                detail = \"configuration error\"\n        else:\n            code = 400\n            message = \"urn:ietf:params:acme:error:serverInternal\"\n            detail = \"Unsupported operation\"\n\n        self.logger.debug(\"CAhandler.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/openxpki_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"openxpki rpc ca handler\"\"\"\nimport math\nimport time\nimport os\nfrom typing import Tuple, Dict\nimport requests\nfrom requests_pkcs12 import Pkcs12Adapter\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    b64_encode,\n    b64_url_recode,\n    build_pem_file,\n    cert_pem2der,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    error_dic_get,\n    handler_config_check,\n    load_config,\n)\nfrom acme_srv.db_handler import DBstore\n\n\nclass CAhandler(object):\n    \"\"\"ejbca rest handler class\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.host = None\n        self.ca_bundle = True\n        self.proxy = None\n        self.request_timeout = 5\n        self.session = None\n        self.cert_profile_name = None\n        self.client_cert = None\n        self.cert_passphrase = None\n        self.endpoint_name = None\n        self.polling_timeout = 0\n        self.rpc_path = \"/rpc/\"\n        self.err_msg_dic = error_dic_get(self.logger)\n        self.dbstore = DBstore(False, self.logger)\n        self.profiles = {}\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.host:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _cert_bundle_create(self, response: Dict[str, str]) -> Tuple[str, str, str]:\n        \"\"\"format bundle\"\"\"\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        if (\n            \"data\" in response\n            and \"certificate\" in response[\"data\"]\n            and \"chain\" in response[\"data\"]\n        ):\n            # create base65 encoded der file\n            cert_raw = b64_encode(\n                self.logger, cert_pem2der(response[\"data\"][\"certificate\"])\n            )\n            cert_bundle = (\n                f'{response[\"data\"][\"certificate\"]}\\n{response[\"data\"][\"chain\"]}'\n            )\n        else:\n            error = \"Malformed response\"\n            self.logger.error(\n                \"Certificate bundle creation failed: malformed response from CA: %s\",\n                response,\n            )\n\n        return (error, cert_bundle, cert_raw)\n\n    def _cert_identifier_get(self, cert_raw: str) -> str:\n        \"\"\"get cert_identifier\"\"\"\n        self.logger.debug(\"CAhandler._cert_identifier_get()\")\n\n        cert_identifier = None\n        result = self.dbstore.certificate_lookup(\n            \"cert_raw\", cert_raw, vlist=(\"name\", \"poll_identifier\")\n        )\n        if \"poll_identifier\" in result and result[\"poll_identifier\"]:\n            cert_identifier = result[\"poll_identifier\"]\n\n        self.logger.debug(\n            \"CAhandler._cert_identifier_get() ended with: %s\", cert_identifier\n        )\n        return cert_identifier\n\n    def _config_server_load(self, config_dic):\n        \"\"\"load server information\"\"\"\n        self.logger.debug(\"CAhandler._config_auth_load()\")\n\n        if \"CAhandler\" in config_dic:\n\n            self.host = config_dic.get(\"CAhandler\", \"host\", fallback=self.host)\n            self.endpoint_name = config_dic.get(\n                \"CAhandler\", \"endpoint_name\", fallback=self.endpoint_name\n            )\n            self.rpc_path = config_dic.get(\n                \"CAhandler\", \"rpc_path\", fallback=self.rpc_path\n            )\n\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\n                        \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                    )\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load request_timeout from config: %s\",\n                    err,\n                )\n                self.request_timeout = 5\n\n        self.logger.debug(\"CAhandler._config_server_load() ended\")\n\n    def _config_ca_load(self, config_dic):\n        \"\"\"load ca information\"\"\"\n        self.logger.debug(\"CAhandler._config_ca_load()\")\n\n        if \"CAhandler\" in config_dic:\n            self.cert_profile_name = config_dic.get(\n                \"CAhandler\", \"cert_profile_name\", fallback=self.cert_profile_name\n            )\n            if \"ca_bundle\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.ca_bundle = config_dic.getboolean(\"CAhandler\", \"ca_bundle\")\n                except Exception as err:\n                    self.logger.debug(\n                        \"CAhandler._config_server_load(): failed to load ca_bundle option: %s\",\n                        err,\n                    )\n                    self.ca_bundle = config_dic.get(\"CAhandler\", \"ca_bundle\")\n\n            if \"polling_timeout\" in config_dic[\"CAhandler\"]:\n                try:\n                    self.polling_timeout = int(\n                        config_dic.get(\"CAhandler\", \"polling_timeout\")\n                    )\n                except Exception as err:\n                    self.logger.error(\n                        \"Failed to load polling_timeout from config: %s\",\n                        err,\n                    )\n        self.logger.debug(\"CAhandler._config_ca_load() ended\")\n\n    def _config_passphrase_load(self, config_dic: Dict[str, str]):\n        \"\"\"load passphrase\"\"\"\n        self.logger.debug(\"CAhandler._config_passphrase_load()\")\n\n        if (\n            \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]\n            or \"cert_passphrase\" in config_dic[\"CAhandler\"]\n        ):\n            if \"cert_passphrase_variable\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\n                    \"CAhandler._config_passphrase_load(): load passphrase from environment variable\"\n                )\n                try:\n                    self.cert_passphrase = os.environ[\n                        config_dic.get(\"CAhandler\", \"cert_passphrase_variable\")\n                    ]\n                except Exception as err:\n                    self.logger.error(\n                        \"Could not load cert_passphrase_variable from environment: %s\",\n                        err,\n                    )\n\n            if \"cert_passphrase\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\n                    \"CAhandler._config_passphrase_load(): load passphrase from config file\"\n                )\n                if self.cert_passphrase:\n                    self.logger.info(\"Overwrite cert_passphrase\")\n                self.cert_passphrase = config_dic.get(\"CAhandler\", \"cert_passphrase\")\n\n        self.logger.debug(\"CAhandler._config_passphrase_load() ended\")\n\n    def _config_session_load(self, config_dic: Dict[str, str]):\n        \"\"\"load session\"\"\"\n        self.logger.debug(\"CAhandler._config_session_load()\")\n\n        with requests.Session() as self.session:\n            # client auth via pem files\n            if (\n                \"client_cert\" in config_dic[\"CAhandler\"]\n                and \"client_key\" in config_dic[\"CAhandler\"]\n            ):\n                self.logger.debug(\n                    \"CAhandler._config_session_load() cert and key in pem format\"\n                )\n                self.session.cert = (\n                    config_dic.get(\"CAhandler\", \"client_cert\"),\n                    config_dic.get(\"CAhandler\", \"client_key\"),\n                )\n\n            else:\n                self._config_passphrase_load(config_dic)\n                if \"client_cert\" in config_dic[\"CAhandler\"] and self.cert_passphrase:\n                    self.session.mount(\n                        self.host,\n                        Pkcs12Adapter(\n                            pkcs12_filename=config_dic[\"CAhandler\"][\"client_cert\"],\n                            pkcs12_password=self.cert_passphrase,\n                        ),\n                    )\n                else:\n                    self.logger.error(\n                        'Configuration incomplete: missing \"client_cert\", \"client_key\", or \"client_passphrase variable\" in config file.'\n                    )\n        self.logger.debug(\"CAhandler._config_session_load() ended\")\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        # load configuration\n        self._config_server_load(config_dic)\n        self._config_ca_load(config_dic)\n        self._config_session_load(config_dic)\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n\n        # load header info\n        # self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n\n        if (\n            \"CAhandler\" in config_dic\n            and \"client_cert\" in config_dic[\"CAhandler\"]\n            and not self.ca_bundle\n        ):\n            self.logger.error(\n                \"Client authentication requires ca_bundle to be enabled in configuration.\"\n            )\n            # load profiles\n            self.profiles = config_profile_load(self.logger, config_dic)\n        # check configuration for completeness\n        variable_dic = self.__dict__\n        for ele in [\"host\", \"cert_profile_name\", \"endpoint_name\"]:\n            if not variable_dic[ele]:\n                self.logger.error(\n                    'Configuration incomplete: parameter \"%s\" is missing in configuration file.',\n                    ele,\n                )\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _enroll(self, data_dic: Dict[str, str]) -> Tuple[str, str, str, str]:\n        \"\"\"enroll operation\"\"\"\n        self.logger.debug(\"CAhandler._enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n        poll_cnt = math.ceil(self.polling_timeout / 10) + 1\n        break_loop = False\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"cert_passphrase\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        cnt = 1\n        while cnt <= poll_cnt:\n            cnt += 1\n            sign_response = self._rpc_post(self.rpc_path + self.endpoint_name, data_dic)\n            if (\n                \"result\" in sign_response\n                and \"state\" in sign_response[\"result\"]\n                and sign_response[\"result\"][\"state\"].upper() == \"SUCCESS\"\n            ):\n                # successful enrollment\n                (error, cert_bundle, cert_raw) = self._cert_bundle_create(\n                    sign_response[\"result\"]\n                )\n                poll_indentifier = sign_response[\"result\"][\"data\"][\"cert_identifier\"]\n                break_loop = True\n            elif (\n                \"result\" in sign_response\n                and \"state\" in sign_response[\"result\"]\n                and sign_response[\"result\"][\"state\"].upper() == \"PENDING\"\n            ):\n                # request to be approved by operator\n                poll_indentifier = sign_response[\"result\"][\"data\"][\"transaction_id\"]\n                self.logger.info(\n                    \"Request pending. Transaction_id: %s Workflow_id: %s\",\n                    poll_indentifier,\n                    sign_response[\"result\"][\"id\"],\n                )\n            else:\n                # ernoll failed\n                error = \"Malformed response\"\n                self.logger.error(\n                    \"Malformed response from CA during enrollment: %s\", sign_response\n                )\n                break_loop = True\n\n            if break_loop:\n                break\n\n            if cnt < poll_cnt:\n                # sleep\n                time.sleep(10)\n\n        self.logger.debug(\n            \"CAhandler._enroll() ended: Poll_identifier: %s\", poll_indentifier\n        )\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def _rpc_post(self, path: str, data_dic: Dict[str, str]) -> Dict[str, str]:\n        \"\"\"enrollment via post request to openxpki RPC interface\"\"\"\n        self.logger.debug(\"CAhandler._rpc_post()\")\n        try:\n            # enroll via rpc\n            response = self.session.post(\n                self.host + path,\n                json=data_dic,\n                verify=self.ca_bundle,\n                proxies=self.proxy,\n                timeout=self.request_timeout,\n            ).json()\n\n        except Exception as err:\n            self.logger.error(\"RPC POST request failed: %s\", err)\n            response = {}\n\n        self.logger.debug(\"CAhandler._rpc_post() ended.\")\n        return response\n\n    def _revoke(self, cert_identifier: str, rev_reason: str) -> Tuple[int, str, str]:\n        \"\"\"exceute revokation via rpc call\"\"\"\n        self.logger.debug(\"CAhandler._revoke()\")\n        code = None\n        message = None\n        detail = None\n\n        if self.host:\n\n            data_dic = {\n                \"method\": \"RevokeCertificate\",\n                \"cert_identifier\": cert_identifier,\n                \"reason_code\": rev_reason,\n            }\n            revocation_response = self._rpc_post(\n                self.rpc_path + self.endpoint_name, data_dic\n            )\n\n            if (\n                \"result\" in revocation_response\n                and \"state\" in revocation_response[\"result\"]\n                and revocation_response[\"result\"][\"state\"].upper() == \"SUCCESS\"\n            ):\n                code = 200\n            else:\n                code = 400\n                message = self.err_msg_dic[\"serverinternal\"]\n                detail = \"Revocation failed\"\n                self.logger.error(\n                    \"Certificate revocation failed: %s\", revocation_response\n                )\n        else:\n            code = 400\n            message = self.err_msg_dic[\"serverinternal\"]\n            detail = \"Incomplete configuration\"\n\n        self.logger.debug(\"CAhandler._revoke() ended with: %s %s\", code, detail)\n        return (code, message, detail)\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        if self.host:\n\n            # check for eab profiling and header_info\n            error = eab_profile_header_info_check(\n                self.logger, self, csr, \"cert_profile_name\"\n            )\n\n            if not error:\n                # prepare the CSR to be signed\n                csr = build_pem_file(\n                    self.logger, None, b64_url_recode(self.logger, csr), None, True\n                )\n\n                data_dic = {\n                    \"method\": \"RequestCertificate\",\n                    \"comment\": \"acme2certifier\",\n                    \"pkcs10\": csr,\n                    \"cert_profile\": self.cert_profile_name,\n                }\n                if self.session:\n                    # enroll via RPC\n                    (error, cert_bundle, cert_raw, poll_indentifier) = self._enroll(\n                        data_dic\n                    )\n                else:\n                    self.logger.error(\n                        \"Configuration incomplete: client authentication is missing.\"\n                    )\n                    error = \"Configuration incomplete\"\n        else:\n            self.logger.error(\"Configuration incomplete: host variable is missing.\")\n            error = \"Configuration incomplete\"\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n        error = handler_config_check(\n            self.logger, self, [\"host\", \"cert_profile_name\", \"endpoint_name\"]\n        )\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, cert: str, rev_reason: str = \"unspecified\", rev_date: str = None\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke(%s: %s)\", rev_reason, rev_date)\n        code = None\n        message = None\n        detail = None\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        cert_raw = b64_url_recode(self.logger, cert)\n        cert_identifier = self._cert_identifier_get(cert_raw)\n\n        if cert_identifier:\n            (code, message, detail) = self._revoke(cert_identifier, rev_reason)\n        else:\n            code = 400\n            message = self.err_msg_dic[\"serverinternal\"]\n            detail = \"Unknown status\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/pkcs7_soap_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"propritary soap_ca_handler\"\"\"\nfrom __future__ import print_function\nimport subprocess\n\n# pylint: disable=e0401\nimport os\nimport binascii\nimport requests\nimport xmltodict\nfrom pyasn1_modules import rfc2314, rfc2315\nfrom pyasn1.codec.der import encoder, decoder\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives.asymmetric import ec\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.primitives.asymmetric import padding\nfrom requests.structures import CaseInsensitiveDict\nfrom acme_srv.helper import (\n    load_config,\n    b64_url_recode,\n    b64_decode,\n    b64_encode,\n    convert_byte_to_string,\n    convert_string_to_byte,\n    generate_random_string,\n)\n\n\ndef binary_read(logger, file_name):\n    \"\"\"dump filename in binary format\"\"\"\n    logger.debug(\"read_binary(%s)\", file_name)\n    # dump csr into file\n    with open(file_name, \"rb\") as reader:\n        content = reader.read()\n\n    return content\n\n\ndef binary_write(logger, file_name, content):\n    \"\"\"dump filename in binary format\"\"\"\n    logger.debug(\"write_binary(%s)\", file_name)\n\n    # dump csr into file\n    with open(file_name, \"wb\") as writer:\n        writer.write(content)\n\n\nclass CAhandler(object):\n    \"\"\"pkcs7 soap ca handler\"\"\"\n\n    def __init__(self, _debug=None, logger=None):\n        self.logger = logger\n        self.soap_srv = None\n        self.profilename = None\n        self.password = None\n        self.signing_cert = None\n        self.signing_key = None\n        self.ca_bundle = False\n        self.email = None\n        self.signing_script_dic = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.soap_srv:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _script_config_load(self, config_dic):\n        \"\"\"load configuriation options for external signing script\"\"\"\n        self.logger.debug(\"CAhandler._script_config_load()\")\n\n        parameters_dic = {\n            \"signing_script\": 0,\n            \"signing_user\": 0,\n            \"signing_alias\": 1,\n            \"signing_csr_path\": 1,\n            \"signing_config_variant\": 1,\n            \"signing_sleep_timer\": 0,\n            \"signing_interpreter\": 0,\n        }\n        for ele, value in parameters_dic.items():\n            if ele in config_dic[\"CAhandler\"]:\n                self.signing_script_dic[ele] = config_dic[\"CAhandler\"][ele]\n            else:\n                if value:\n                    self.logger.error(\n                        \"%s option is missing in configuration file.\",\n                        ele,\n                    )\n\n    def _self_signing_config_load(self, config_dic):\n        \"\"\"load configuriation options for self signing\"\"\"\n        self.logger.debug(\"CAhandler._self_signing_config_load()\")\n\n        if \"signing_cert\" in config_dic[\"CAhandler\"]:\n            if os.path.exists(config_dic[\"CAhandler\"][\"signing_cert\"]):\n                with open(config_dic[\"CAhandler\"][\"signing_cert\"], \"rb\") as open_file:\n                    self.signing_cert = x509.load_pem_x509_certificate(\n                        open_file.read(), default_backend()\n                    )\n            else:\n                self.logger.error(\n                    \"Signing certificate file not found: %s\",\n                    config_dic[\"CAhandler\"][\"signing_cert\"],\n                )\n        else:\n            self.logger.error(\n                \"Signing certificate option is missing in configuration file.\"\n            )\n\n        if \"password\" in config_dic[\"CAhandler\"]:\n            self.password = convert_string_to_byte(config_dic[\"CAhandler\"][\"password\"])\n\n        if \"signing_key\" in config_dic[\"CAhandler\"]:\n            if os.path.exists(config_dic[\"CAhandler\"][\"signing_key\"]):\n                with open(config_dic[\"CAhandler\"][\"signing_key\"], \"rb\") as open_file:\n                    self.signing_key = serialization.load_pem_private_key(\n                        open_file.read(),\n                        password=self.password,\n                        backend=default_backend(),\n                    )\n            else:\n                self.logger.error(\n                    \"Signing key file not found: %s\",\n                    config_dic[\"CAhandler\"][\"signing_key\"],\n                )\n        else:\n            self.logger.error(\"Signing key option is missing in configuration file.\")\n\n    def _global_config_load(self, config_dic):\n        \"\"\"load configuriation options for external signing script\"\"\"\n        self.logger.debug(\"CAhandler._global_config_load()\")\n\n        if \"soap_srv\" in config_dic[\"CAhandler\"]:\n            self.soap_srv = config_dic[\"CAhandler\"][\"soap_srv\"]\n        else:\n            self.logger.error(\n                \"SOAP server URL (soap_srv) is missing in configuration file.\"\n            )\n\n        if \"ca_bundle\" in config_dic[\"CAhandler\"]:\n            self.ca_bundle = config_dic[\"CAhandler\"][\"ca_bundle\"]\n        else:\n            self.logger.warning(\"SOAP server certificate validation is disabled.\")\n\n        if \"profilename\" in config_dic[\"CAhandler\"]:\n            self.profilename = config_dic[\"CAhandler\"][\"profilename\"]\n        else:\n            self.logger.error(\n                \"Profile name (profilename) is missing in configuration file.\"\n            )\n\n        if \"email\" in config_dic[\"CAhandler\"]:\n            self.email = config_dic[\"CAhandler\"][\"email\"]\n        else:\n            self.logger.error(\"Email option is missing in configuration file.\")\n\n    def _config_load(self):\n        # pylint: disable=R0912\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n\n            # load global options needed for both configurations\n            self._global_config_load(config_dic)\n\n            if \"signing_script\" in config_dic[\"CAhandler\"]:\n                self.logger.debug(\n                    \"CAhandler._config_load(): CSR-signing by external script\"\n                )\n                self._script_config_load(config_dic)\n            else:\n                self.logger.debug(\"CAhandler._config_load(): CSR-signing by CA handler\")\n                self._self_signing_config_load(config_dic)\n\n        else:\n            self.logger.error(\"CAhandler section is missing in configuration file.\")\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _cert_decode(self, cert):\n        self.logger.debug(\"CAhandler._cert_decode()\")\n        return decoder.decode(  # NOSONAR\n            cert.public_bytes(serialization.Encoding.DER),\n            asn1Spec=rfc2315.Certificate(),\n        )\n\n    def _sign(self, key, payload):\n        \"\"\"Signs the payload with the specified key\"\"\"\n\n        signature_algorithm = rfc2314.AlgorithmIdentifier()\n\n        if isinstance(key, rsa.RSAPrivateKey):\n            # sha256WithRSAEncryption. MUST have ASN.1 NULL in the parameters field\n            signature_algorithm.setComponentByName(\n                \"algorithm\", (1, 2, 840, 113549, 1, 1, 11)\n            )\n            signature_algorithm.setComponentByName(\"parameters\", \"\\x05\\x00\")\n            signature = key.sign(payload, padding.PKCS1v15(), hashes.SHA256())\n        elif isinstance(key, ec.EllipticCurvePrivateKey):\n            # ecdsaWithSHA256. MUST omit the parameters field\n            signature_algorithm.setComponentByName(\n                \"algorithm\", (1, 2, 840, 10045, 4, 3, 2)\n            )\n            signature = key.sign(payload, ec.ECDSA(hashes.SHA256()))\n        else:\n            signature = None\n            signature_algorithm = None\n\n        return signature, signature_algorithm\n\n    def _pkcs7_create(self, cert, csr, private_key):\n        \"\"\"Creates the PKCS7 structure and signs it\"\"\"\n        self.logger.debug(\"CAhandler._pkcs7_create()\")\n        content_info = rfc2315.ContentInfo()\n        content_info.setComponentByName(\"contentType\", rfc2315.data)\n        content_info.setComponentByName(\n            \"content\", encoder.encode(rfc2315.Data(csr))\n        )  # NOSONAR\n\n        issuer_and_serial = rfc2315.IssuerAndSerialNumber()\n        issuer_and_serial.setComponentByName(\n            \"issuer\", cert[0][\"tbsCertificate\"][\"issuer\"]\n        )\n        issuer_and_serial.setComponentByName(\n            \"serialNumber\", cert[0][\"tbsCertificate\"][\"serialNumber\"]\n        )\n\n        raw_signature, _ = self._sign(private_key, csr)\n        signature = rfc2314.univ.OctetString(\n            hexValue=binascii.hexlify(raw_signature).decode(\"ascii\")\n        )\n\n        # Microsoft adds parameters with ASN.1 NULL encoding here,\n        # but according to rfc5754 they should be absent:\n        # \"Implementations MUST generate SHA2 AlgorithmIdentifiers with absent parameters.\"\n        sha2 = rfc2315.AlgorithmIdentifier()\n        sha2.setComponentByName(\"algorithm\", (2, 16, 840, 1, 101, 3, 4, 2, 1))\n\n        alg_from_cert = cert[0][\"tbsCertificate\"][\"subjectPublicKeyInfo\"][\"algorithm\"][\n            \"algorithm\"\n        ]\n        digest_encryption_algorithm = rfc2315.AlgorithmIdentifier()\n        digest_encryption_algorithm.setComponentByName(\"algorithm\", alg_from_cert)\n        digest_encryption_algorithm.setComponentByName(\"parameters\", \"\\x05\\x00\")\n\n        signer_info = rfc2315.SignerInfo()\n        signer_info.setComponentByName(\"version\", 1)\n        signer_info.setComponentByName(\"issuerAndSerialNumber\", issuer_and_serial)\n        signer_info.setComponentByName(\"digestAlgorithm\", sha2)\n        signer_info.setComponentByName(\n            \"digestEncryptionAlgorithm\", digest_encryption_algorithm\n        )\n        signer_info.setComponentByName(\"encryptedDigest\", signature)\n\n        signer_infos = rfc2315.SignerInfos().setComponents(signer_info)\n\n        digest_algorithms = rfc2315.DigestAlgorithmIdentifiers().setComponents(sha2)\n\n        extended_cert_or_cert = rfc2315.ExtendedCertificateOrCertificate()\n        extended_cert_or_cert.setComponentByName(\"certificate\", cert[0])\n\n        extended_certs_and_cert = rfc2315.ExtendedCertificatesAndCertificates().subtype(\n            implicitTag=rfc2315.tag.Tag(\n                rfc2315.tag.tagClassContext, rfc2315.tag.tagFormatConstructed, 0\n            )\n        )\n        extended_certs_and_cert.setComponents(extended_cert_or_cert)\n\n        signed_data = rfc2315.SignedData()\n        signed_data.setComponentByName(\"version\", 1)\n        signed_data.setComponentByName(\"digestAlgorithms\", digest_algorithms)\n        signed_data.setComponentByName(\"contentInfo\", content_info)\n        signed_data.setComponentByName(\"certificates\", extended_certs_and_cert)\n        signed_data.setComponentByName(\"signerInfos\", signer_infos)\n\n        outer_content_info = rfc2315.ContentInfo()\n        outer_content_info.setComponentByName(\"contentType\", rfc2315.signedData)\n        outer_content_info.setComponentByName(\n            \"content\", encoder.encode(signed_data)\n        )  # NOSONAR\n\n        error = None\n        self.logger.debug(\"CAhandler._pkcs7_create() ended\")\n        return (error, encoder.encode(outer_content_info))  # NOSONAR\n\n    def _soaprequest_build(self, pkcs7):\n        \"\"\"build soap request payload\"\"\"\n        self.logger.debug(\"CAhandler._soaprequest_build()\")\n        data = f\"\"\"\n<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:aur=\"http://monetplus.cz/services/kb/aurora\">\n<soapenv:Header/>\n<soapenv:Body>\n    <aur:RequestCertificate>\n        <aur:request>\n            <aur:ProfileName>{self.profilename}</aur:ProfileName>\n            <aur:CertificateRequestRaw>{pkcs7}</aur:CertificateRequestRaw>\n            <aur:Email>{self.email}</aur:Email>\n            <aur:ReturnCertificateCaChain>true</aur:ReturnCertificateCaChain>\n        </aur:request>\n    </aur:RequestCertificate>\n</soapenv:Body>\n</soapenv:Envelope>\n\"\"\"\n        return data\n\n    def _soaprequest_send(self, payload):\n        \"\"\"forward csr to ca server\"\"\"\n        self.logger.debug(\"CAhandler._soaprequest_send()\")\n\n        headers = CaseInsensitiveDict()\n        headers[\"Content-Type\"] = \"application/soap+xml\"\n\n        b64_cert_bundle = None\n        error = None\n\n        senvelope_field_name = \"s:Envelope\"\n        sbody_field_name = \"s:Body\"\n\n        try:\n            resp = requests.post(\n                self.soap_srv,\n                headers=headers,\n                verify=self.ca_bundle,\n                data=payload,\n                timeout=20,\n            )\n            if resp.status_code == 200:\n                soap_dic = xmltodict.parse(resp.text)\n                try:\n                    b64_cert_bundle = soap_dic[senvelope_field_name][sbody_field_name][\n                        \"RequestCertificateResponse\"\n                    ][\"RequestCertificateResult\"][\"IssuedCertificate\"]\n                except Exception:\n                    self.logger.error(\"XML parsing error in SOAP response from CA.\")\n                    self.logger.debug(\n                        \"CAhandler._soaprequest_send() xml2dict: %s\", resp.text\n                    )\n                    error = \"Parsing error\"\n            else:\n                self.logger.error(\n                    \"CA server returned HTTP error status: %s\",\n                    resp.status_code,\n                )\n                error = \"Server error\"\n                try:\n                    soap_dic = xmltodict.parse(resp.text)\n                    self.logger.error(\n                        \"SOAP response contains faultcode: %s\",\n                        soap_dic[senvelope_field_name][sbody_field_name][\"s:Fault\"][\n                            \"faultcode\"\n                        ],\n                    )\n                    self.logger.error(\n                        \"SOAP response contains faultstring: %s\",\n                        soap_dic[senvelope_field_name][sbody_field_name][\"s:Fault\"][\n                            \"faultstring\"\n                        ],\n                    )\n                except Exception:\n                    self.logger.error(\n                        \"Unknown error while parsing SOAP response from CA.\"\n                    )\n                    self.logger.debug(\n                        \"CAhandler._soaprequest_send() unk: %s\", resp.text\n                    )\n\n        except Exception as err:\n            self.logger.error(\"SOAP request to CA failed: %s\", err)\n            error = \"Connection error\"\n            payload = None  # lgtm [py/unused-local-variable]\n            resp = None  # lgtm [py/unused-local-variable]\n\n        return (error, b64_cert_bundle)\n\n    def _get_certificate(self, signature_block_file):\n        \"\"\"Extracts a DER certificate from JAR Signature's \"Signature Block File\".\n\n        :param signature_block_file: file bytes (as string) representing the\n        certificate, as read directly out of the APK/ZIP\n\n        :return: A binary representation of the certificate's public key,\n        or None in case of error\n\n        \"\"\"\n        content = decoder.decode(signature_block_file, asn1Spec=rfc2315.ContentInfo())[\n            0\n        ]  # NOSONAR\n        if (\n            content.getComponentByName(\"contentType\") != rfc2315.signedData\n        ):  # pragma: no cover\n            return None  # pragma: no cover\n        content = decoder.decode(\n            content.getComponentByName(\"content\"), asn1Spec=rfc2315.SignedData()\n        )[\n            0\n        ]  # NOSONAR\n\n        cert_list = []\n        for cert in content.getComponentByName(\"certificates\"):\n            cert_obj = x509.load_der_x509_certificate(\n                encoder.encode(cert), default_backend()\n            )  # NOSONAR\n            cert_pem = cert_obj.public_bytes(serialization.Encoding.PEM)\n            cert_list.append(convert_byte_to_string(cert_pem))\n\n        return cert_list\n\n    def _certraw_get(self, pem_data):\n        \"\"\"get raw certificate as required by a2c\"\"\"\n        self.logger.debug(\"CAhandler._certraw_get()\")\n\n        cert = x509.load_pem_x509_certificate(\n            convert_string_to_byte(pem_data), default_backend()\n        )\n        # DER cert\n        cert_val = cert.public_bytes(serialization.Encoding.DER)\n\n        return b64_encode(self.logger, cert_val)\n\n    def _pkcs7_signing_config_verify(self):\n        \"\"\"verify external signing configuration\"\"\"\n        self.logger.debug(\"CAhandler._pkcs7_signing_config_verify\")\n\n        error = None\n        signing_parameters = [\n            \"signing_script\",\n            \"signing_alias\",\n            \"signing_csr_path\",\n            \"signing_config_variant\",\n        ]\n\n        for ele in signing_parameters:\n            if ele not in self.signing_script_dic:\n                error = f\"signing config incomplete: option {ele} is missing\"\n                break\n            if ele == \"signing_csr_path\":\n                if not os.path.isdir(self.signing_script_dic[ele]):\n                    error = (\n                        f\"signing_csr_path {ele} does not exist or is not a directory\"\n                    )\n                    break\n\n        self.logger.debug(\n            \"CAhandler._pkcs7_signing_config_verify() returned with %s\", error\n        )\n        return error\n\n    def _signing_command_build(self, csr_unsigned, csr_signed):\n        \"\"\"build signing command\"\"\"\n        self.logger.debug(\"CAhandler._signing_command_build(%s)\", csr_unsigned)\n\n        if \"signing_script\" in self.signing_script_dic:\n            if \"signing_user\" in self.signing_script_dic:\n                cmd_list = [\"sudo\", self.signing_script_dic[\"signing_user\"]]\n            else:\n                cmd_list = []\n\n            if \"signing_interpreter\" in self.signing_script_dic:\n                cmd_list.append(self.signing_script_dic[\"signing_interpreter\"])\n\n            # build command\n            cmd_list.append(self.signing_script_dic[\"signing_script\"])\n            cmd_list.extend([csr_unsigned, csr_signed])\n            if (\n                \"signing_alias\" in self.signing_script_dic\n                and \"signing_config_variant\" in self.signing_script_dic\n            ):\n                cmd_list.extend(\n                    [\n                        self.signing_script_dic[\"signing_alias\"],\n                        self.signing_script_dic[\"signing_config_variant\"],\n                    ]\n                )\n        else:\n            cmd_list = []\n\n        self.logger.debug(\n            \"CAhandler._signing_command_build() ended with: %s\", \" \".join(cmd_list)\n        )\n        return cmd_list\n\n    def _pkcs7_sign_external(self, csr):\n        \"\"\"sign csr by using an external script\"\"\"\n        self.logger.debug(\"CAhandler._pkcs7_sign_external\")\n\n        # check external signing configuration\n        signing_check = self._pkcs7_signing_config_verify()\n        if signing_check:\n            self.logger.error(\n                \"External signing configuration is incomplete: %s\", signing_check\n            )\n            rcode = \"Config incomplete\"\n            pkcs7_bundle = None\n        else:\n            # define temporary filenames\n            _fname = generate_random_string(self.logger, 12)\n            unsigned_filename = (\n                f'{self.signing_script_dic[\"signing_csr_path\"]}/{_fname}.der'\n            )\n            signed_filename = (\n                f'{self.signing_script_dic[\"signing_csr_path\"]}/{_fname}_signed.der'\n            )\n\n            # build signing command\n            signing_cmd = self._signing_command_build(\n                unsigned_filename, signed_filename\n            )\n\n            # dump csr to file\n            binary_write(self.logger, unsigned_filename, csr)\n\n            # call signing script with parameters\n            rcode = subprocess.call(signing_cmd)\n            if not rcode:\n                pkcs7_bundle = binary_read(self.logger, signed_filename)\n            else:\n                self.logger.error(\"Certificate enrollment aborted: %s\", rcode)\n                pkcs7_bundle = None\n\n            # delete temporary files\n            for ele in (unsigned_filename, signed_filename):\n                if os.path.isfile(ele):\n                    os.remove(ele)\n        self.logger.debug(\n            \"CAhandler._pkcs7_sign_external() ended with error: %s\", rcode\n        )\n        return (rcode, pkcs7_bundle)\n\n    def enroll(self, csr):\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        # convert csr to DER format\n        csr_der = b64_decode(self.logger, b64_url_recode(self.logger, csr))\n\n        if self.signing_script_dic:\n            # signing by external script\n            (error, pkcs7_bundle) = self._pkcs7_sign_external(csr_der)\n        else:\n            # create pkcs7 bundle\n            decoded_cert = self._cert_decode(self.signing_cert)\n            # signing by handler\n            (error, pkcs7_bundle) = self._pkcs7_create(\n                decoded_cert, csr_der, self.signing_key\n            )\n\n        if not error:\n            # build and soap request to be send to ca server\n            payload = self._soaprequest_build(b64_encode(self.logger, pkcs7_bundle))\n            (error, b64_cert_bundle) = self._soaprequest_send(payload)\n        else:\n            self.logger.error(\"CAhandler.enroll() aborted with error: %s\", error)\n            b64_cert_bundle = None  # lgtm [py/unused-local-variable]\n\n        if not error and b64_cert_bundle:\n            # extract certificates from pkcs7 bundle we got as response\n            certificate_list = self._get_certificate(\n                b64_decode(self.logger, b64_cert_bundle)\n            )\n\n            # create pem bundle and raw file\n            cert_bundle = \"\".join(certificate_list)\n            cert_raw = self._certraw_get(certificate_list[0])\n        else:\n            if error:\n                self.logger.error(\n                    \"SOAP request to CA failed: %s\",\n                    error,\n                )\n            else:\n                self.logger.error(\n                    \"SOAP request to CA did not return a certificate bundle.\"\n                )\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def poll(self, _cert_name, poll_identifier, _csr):\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(self, _cert, _rev_reason, _rev_date):\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload):\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/skeleton_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"skeleton for customized CA handler\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple\n\n# pylint: disable=e0401\nfrom acme_srv.helper import load_config, header_info_get\n\n\nclass CAhandler(object):\n    \"\"\"EST CA  handler\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.parameter = None\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.parameter:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n        self.parameter = config_dic.get(\n            \"CAhandler\", \"parameter\", fallback=self.parameter\n        )\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _stub_func(self, parameter: str):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._stub_func(%s)\", parameter)\n\n        self.logger.debug(\"CAhandler._stub_func() ended\")\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        # optional: lookup http header information from request\n        qset = header_info_get(self.logger, csr=csr)\n        if qset:\n            self.logger.info(qset[-1][\"header_info\"])\n        # this is a stub function, replace with actual implementation\n        self._stub_func(csr)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n\n        return (error, cert_bundle, cert_raw, poll_indentifier)\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        # check if CA is reachable and the CA handler configured correctly\n        # this is a stub function, replace with actual implementation\n        error = self._stub_func(\"text\")\n\n        self.logger.debug(\"CAhandler.handler_check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n        self._stub_func(cert_name)\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, _cert: str, _rev_reason: str, _rev_date: str\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = 500\n        message = \"urn:ietf:params:acme:error:serverInternal\"\n        detail = \"Revocation is not supported.\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = None\n        cert_bundle = None\n        cert_raw = None\n        self._stub_func(payload)\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/vault_ca_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"CA handler using HashiCorp Vault\"\"\"\nfrom __future__ import print_function\nfrom typing import Tuple, Dict, List\nimport datetime\nimport os\nimport requests\nimport json\nfrom requests_pkcs12 import Pkcs12Adapter\n\n# pylint: disable=e0401\nfrom acme_srv.helper import (\n    b64_encode,\n    b64_url_recode,\n    build_pem_file,\n    cert_pem2der,\n    cert_serial_get,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    load_config,\n    uts_now,\n    uts_to_date_utc,\n    csr_cn_lookup,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    config_proxy_load,\n    enrollment_config_log,\n    request_operation,\n)\n\n\nCONTENT_TYPE = \"application/json\"\n\n\nclass CAhandler(object):\n    \"\"\"Hashicorp vault handler\"\"\"\n\n    def __init__(self, _debug: bool = None, logger: object = None):\n        self.logger = logger\n        self.vault_url = None\n        self.vault_path = None\n        self.vault_role = None\n        self.vault_token = None\n        self.issuer_ref = None\n        self.cert_validity_days = 365\n        self.request_timeout = 20\n        self.ca_bundle = True\n\n        self.header_info_field = False\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n        self.proxy = {}\n\n    def __enter__(self):\n        \"\"\"Makes CAhandler a Context Manager\"\"\"\n        if not self.vault_url:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"close the connection at the end of the context\"\"\"\n\n    def _api_get(self, url: str) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_get()\")\n        headers = {\"Content-Type\": CONTENT_TYPE, \"X-Vault-Token\": self.vault_token}\n\n        code, content = request_operation(\n            self.logger,\n            method=\"get\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=None,\n        )\n        self.logger.debug(\"CAhandler._api_get() ended with code: %s\", code)\n        return code, content\n\n    def _api_post(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_post()\")\n        headers = {\"Content-Type\": CONTENT_TYPE, \"X-Vault-Token\": self.vault_token}\n        code, content = request_operation(\n            self.logger,\n            method=\"post\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n            verify=self.ca_bundle,\n        )\n        self.logger.debug(\"CAhandler._api_post() ended with code: %s\", code)\n        return code, content\n\n    def _api_put(self, url: str, data: Dict[str, str]) -> Tuple[int, Dict[str, str]]:\n        \"\"\"post data to API\"\"\"\n        self.logger.debug(\"CAhandler._api_put()\")\n        headers = {\"Content-Type\": CONTENT_TYPE, \"X-Vault-Token\": self.vault_token}\n        code, content = request_operation(\n            self.logger,\n            method=\"put\",\n            url=url,\n            headers=headers,\n            proxy=self.proxy,\n            timeout=self.request_timeout,\n            payload=data,\n        )\n\n        self.logger.debug(\"CAhandler._api_put() ended with code: %s\", code)\n        return code, content\n\n    def _config_check(self) -> str:\n        \"\"\"check if config is valid\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n        error = None\n\n        error = None\n        for ele in [\"vault_url\", \"vault_path\", \"vault_role\", \"vault_token\"]:\n            if not getattr(self, ele):\n\n                error = f\"{ele} parameter is missing in config file\"\n                self.logger.error(\"Configuration check ended with error: %s\", error)\n                break\n\n        self.logger.debug(\"CAhandler._config_check() ended with %s\", error)\n        return error\n\n    def _config_load(self):\n        \"\"\"load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"CAhandler\")\n        if \"CAhandler\" in config_dic:\n            self.vault_url = config_dic.get(\"CAhandler\", \"vault_url\", fallback=None)\n            self.vault_path = config_dic.get(\"CAhandler\", \"vault_path\", fallback=None)\n            self.vault_role = config_dic.get(\"CAhandler\", \"vault_role\", fallback=None)\n            self.vault_token = config_dic.get(\"CAhandler\", \"vault_token\", fallback=None)\n            self.issuer_ref = config_dic.get(\"CAhandler\", \"issuer_ref\", fallback=None)\n            try:\n                self.request_timeout = int(\n                    config_dic.get(\n                        \"CAhandler\", \"request_timeout\", fallback=self.request_timeout\n                    )\n                )\n            except Exception as err:\n                self.logger.error(\"Failed to parse request_timeout parameter: %s\", err)\n            try:\n                self.cert_validity_days = int(\n                    config_dic.get(\n                        \"CAhandler\",\n                        \"cert_validity_days\",\n                        fallback=self.cert_validity_days,\n                    )\n                )\n            except Exception as err:\n                self.logger.error(\n                    \"Failed to parse cert_validity_days %s parameter\",\n                    err,\n                )\n\n            try:\n                self.ca_bundle = config_dic.getboolean(\"CAhandler\", \"ca_bundle\")\n            except Exception:\n                self.ca_bundle = config_dic.get(\n                    \"CAhandler\", \"ca_bundle\", fallback=self.ca_bundle\n                )\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n\n        # load proxies\n        self.proxy = config_proxy_load(self.logger, config_dic, self.vault_url)\n\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n        self.logger.debug(\"CAhandler._config_load() ended\")\n\n    def _csr_check(self, csr: str) -> str:\n        \"\"\"check csr\"\"\"\n        self.logger.debug(\"CAhandler._csr_check()\")\n\n        error = eab_profile_header_info_check(self.logger, self, csr, \"vault_role\")\n\n        self.logger.debug(\"CAhandler._csr_check() ended with: %s\", error)\n        return error\n\n    def _enrollment_request_prepare(self, csr: str) -> Tuple[str, str, str]:\n        \"\"\"prepare enrollment request\"\"\"\n        self.logger.debug(\"CAhandler._enrollment_request_prepare()\")\n\n        csr_cn = csr_cn_lookup(self.logger, csr)\n        # reformat csr\n        # prepare the CSR to be signed\n        csr = build_pem_file(\n            self.logger, None, b64_url_recode(self.logger, csr), None, True\n        )\n\n        self.logger.debug(\"CAhandler._enrollment_request_prepare() ended\")\n        return csr_cn, csr\n\n    def _preconfig_check(self, csr: str) -> str:\n        \"\"\"check if config and csr is valid\"\"\"\n        self.logger.debug(\"CAhandler._preconfig_check()\")\n\n        error = self._config_check()\n        if not error:\n            error = self._csr_check(csr)\n\n        self.logger.debug(\"CAhandler._preconfig_check() ended with %s\", error)\n        return error\n\n    def enroll(self, csr: str) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        error = None\n        cert_raw = None\n        poll_indentifier = None\n\n        # check config and csr\n        error = self._preconfig_check(csr)\n\n        if not error:\n            csr_cn, csr = self._enrollment_request_prepare(csr)\n\n            data_dic = {\n                \"csr\": csr,\n                \"common_name\": csr_cn,\n            }\n            if self.issuer_ref:\n                enroll_url = f\"{self.vault_url}/v1/{self.vault_path}/issuer/{self.issuer_ref}/sign/{self.vault_role}\"\n            else:\n                enroll_url = (\n                    f\"{self.vault_url}/v1/{self.vault_path}/sign/{self.vault_role}\"\n                )\n\n            if self.enrollment_config_log:\n                self.enrollment_config_log_skip_list.extend(\n                    [\n                        \"vault_token\",\n                        \"enrollment_config_log_skip_list\",\n                        \"enrollment_config_log\",\n                    ]\n                )\n                enrollment_config_log(\n                    self.logger, self, self.enrollment_config_log_skip_list\n                )\n\n            # enroll certificate\n            code, content = self._api_post(enroll_url, data_dic)\n\n            if (\n                code in (200, 201)\n                and content.get(\"data\").get(\"certificate\")\n                and content.get(\"data\").get(\"ca_chain\")\n            ):\n                cert_bundle = f'{content[\"data\"][\"certificate\"]}\\n' + \"\\n\".join(\n                    content[\"data\"][\"ca_chain\"]\n                )\n                cert_raw = b64_encode(\n                    self.logger, cert_pem2der(content[\"data\"][\"certificate\"])\n                )\n            else:\n                error = (\n                    json.dumps(content[\"errors\"])\n                    if \"errors\" in content\n                    else json.dumps(content)\n                )\n                self.logger.error(\"Failed to enroll certificate: %s\", error)\n\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return error, cert_bundle, cert_raw, poll_indentifier\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        error = self._config_check()\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def poll(\n        self, _cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self,\n        certificate_raw: str,\n        _rev_reason: str = \"unspecified\",\n        _rev_date: str = uts_to_date_utc(uts_now()),\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        code = None\n        message = None\n        detail = None\n\n        cert_serial = cert_serial_get(self.logger, certificate_raw, hexformat=True)\n\n        if cert_serial:\n            self.logger.debug(\"Certificate serial number found: %s\", cert_serial)\n\n            # modify handler configuration in case of eab profiling\n            if self.eab_profiling:\n                eab_profile_revocation_check(self.logger, self, certificate_raw)\n\n            if self.enrollment_config_log:\n                # log enrollment config\n                self.enrollment_config_log_skip_list.extend(\n                    [\n                        \"vault_token\",\n                        \"enrollment_config_log_skip_list\",\n                        \"enrollment_config_log\",\n                    ]\n                )\n                enrollment_config_log(\n                    self.logger, self, self.enrollment_config_log_skip_list\n                )\n\n            # reformat serial number\n            cert_serial = \":\".join(\n                cert_serial[i : i + 2] for i in range(0, len(cert_serial), 2)\n            ).lower()\n            data_dic = {\"serial_number\": f\"{cert_serial}\"}\n            revoke_url = f\"{self.vault_url}/v1/{self.vault_path}/revoke\"\n            code, content = self._api_post(revoke_url, data_dic)\n            if code not in (200, 201):\n                detail = (\n                    json.dumps(content[\"errors\"])\n                    if \"errors\" in content\n                    else json.dumps(content)\n                )\n                self.logger.error(\"Failed to revoke certificate: %s\", detail)\n        else:\n            self.logger.error(\"Failed to get certificate serial number\")\n            code = 500\n            detail = \"Failed to parse certificate serial\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, _payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/ca_handler/xca_ca_handler.py",
    "content": "# pylint: disable=e0401, c0302\n# -*- coding: utf-8 -*-\n\"\"\"handler for xca ca handler\"\"\"\nfrom __future__ import print_function\nimport os\nimport sqlite3\nimport uuid\nimport json\nimport datetime\nfrom typing import List, Tuple, Dict\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization, hashes\nfrom cryptography.x509 import (\n    BasicConstraints,\n    ExtendedKeyUsage,\n    SubjectKeyIdentifier,\n    AuthorityKeyIdentifier,\n    KeyUsage,\n    SubjectAlternativeName,\n)\nfrom cryptography.x509.oid import ExtendedKeyUsageOID\nfrom OpenSSL import crypto as pyossslcrypto\nfrom acme_srv.helper import (\n    b64_decode,\n    b64_encode,\n    b64_url_recode,\n    build_pem_file,\n    cert_serial_get,\n    config_eab_profile_load,\n    config_enroll_config_log_load,\n    config_headerinfo_load,\n    config_profile_load,\n    convert_byte_to_string,\n    convert_string_to_byte,\n    csr_cn_get,\n    csr_san_get,\n    eab_profile_header_info_check,\n    eab_profile_revocation_check,\n    enrollment_config_log,\n    error_dic_get,\n    load_config,\n    uts_now,\n    uts_to_date_utc,\n)\n\n\n# Define constants\nDEFAULT_DATE_FORMAT = \"%Y%m%d%H%M%SZ\"\nCOLUMN_NOT_IN_TABLE_MSG = \"column: %s not in %s table\"\n\n\ndef dict_from_row(row):\n    \"\"\"small helper to convert the output of a \"select\" command into a dictionary\"\"\"\n    return dict(zip(row.keys(), row))\n\n\nclass CAhandler(object):\n    \"\"\"CA  handler\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None):\n        self.debug = debug\n        self.logger = logger\n        self.xdb_file = None\n        self.xdb_permission = \"660\"\n        self.passphrase = None\n        self.issuing_ca_name = None\n        self.issuing_ca_key = None\n        self.cert_validity_days = 365\n        self.ca_cert_chain_list = []\n        self.template_name = None\n        self.header_info_field = None\n        self.eab_handler = None\n        self.eab_profiling = False\n        self.enrollment_config_log = False\n        self.enrollment_config_log_skip_list = []\n        self.profiles = {}\n\n    def __enter__(self):\n        \"\"\"Makes ACMEHandler a Context Manager\"\"\"\n        if not self.xdb_file:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _asn1_stream_parse(self, asn1_stream: str = None) -> Dict[str, str]:\n        \"\"\"parse asn_string\"\"\"\n\n        self.logger.debug(\"CAhandler._asn1_stream_parse()\")\n        oid_dic = {\n            \"2.5.4.3\": \"commonName\",\n            \"2.5.4.4\": \"surname\",\n            \"2.5.4.5\": \"serialNumber\",\n            \"2.5.4.6\": \"countryName\",\n            \"2.5.4.7\": \"localityName\",\n            \"2.5.4.8\": \"stateOrProvinceName\",\n            \"2.5.4.9\": \"streetAddress\",\n            \"2.5.4.10\": \"organizationName\",\n            \"2.5.4.11\": \"organizationalUnitName\",\n            \"2.5.4.12\": \"title\",\n            \"2.5.4.13\": \"description\",\n            \"2.5.4.42\": \"givenName\",\n        }\n\n        dn_dic = {}\n        if asn1_stream:\n\n            # cut first 8 bytes which are bogus\n            # asn1_stream = asn1_stream[8:]\n\n            # split stream\n            stream_list = asn1_stream.split(b\"\\x06\\x03\\x55\")\n            # we have to remove the first element from list as it contains junk\n            stream_list.pop(0)\n\n            for ele in stream_list:\n                oid = f\"2.5.{ele[0]}.{ele[1]}\"\n                if oid in oid_dic:\n                    value_len = ele[3]\n                    value = ele[4 : 4 + value_len]\n                    dn_dic[oid_dic[oid]] = value.decode(\"utf-8\")\n\n            self.logger.debug(\"CAhandler._asn1_stream_parse() ended: %s\", bool(dn_dic))\n        return dn_dic\n\n    def _ca_cert_load(self) -> Tuple[object, int]:\n        \"\"\"load ca key from database\"\"\"\n        self.logger.debug(\"CAhandler._ca_cert_load({%s)\", self.issuing_ca_name)\n\n        # query database for key\n        self._db_open()\n        pre_statement = \"\"\"SELECT * from view_certs WHERE name LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [self.issuing_ca_name])\n        try:\n            db_result = dict_from_row(self.cursor.fetchone())\n        except Exception:\n            self.logger.error(\n                \"Certificate lookup in database failed: %s\", self.cursor.fetchone()\n            )\n            db_result = {}\n        self._db_close()\n\n        ca_cert = None\n        ca_id = None\n\n        if \"cert\" in db_result:\n            try:\n                ca_cert = x509.load_der_x509_certificate(\n                    b64_decode(self.logger, db_result[\"cert\"]),\n                    backend=default_backend(),\n                )\n                ca_id = db_result[\"id\"]\n            except Exception as err_:\n                self.logger.error(\n                    \"Failed to load CA certificate from database: %s\", err_\n                )\n\n        return (ca_cert, ca_id)\n\n    def _ca_key_load(self) -> object:\n        \"\"\"load ca key from database\"\"\"\n        self.logger.debug(\"CAhandler._ca_key_load(%s)\", self.issuing_ca_key)\n\n        # query database for key\n        self._db_open()\n        pre_statement = \"\"\"SELECT * from view_private WHERE name LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [self.issuing_ca_key])\n        try:\n            db_result = dict_from_row(self.cursor.fetchone())\n        except Exception as err:\n            self.logger.error(\"Failed to load CA private key from database: %s\", err)\n            db_result = {}\n        self._db_close()\n\n        ca_key = None\n        if db_result and \"private\" in db_result:\n            try:\n                private_key = f'-----BEGIN ENCRYPTED PRIVATE KEY-----\\n{db_result[\"private\"]}\\n-----END ENCRYPTED PRIVATE KEY-----'\n                ca_key = serialization.load_pem_private_key(\n                    convert_string_to_byte(private_key),\n                    password=convert_string_to_byte(self.passphrase),\n                    backend=default_backend(),\n                )\n            except Exception as err_:\n                self.logger.error(\n                    \"Failed to load CA private key from database: %s\", err_\n                )\n        else:\n            self.logger.error(\"Failed to load CA private key: %s\", db_result)\n\n        self.logger.debug(\"CAhandler._ca_key_load() ended\")\n        return ca_key\n\n    def _ca_load(self) -> Tuple[object, object, int]:\n        \"\"\"load ca key and cert\"\"\"\n        self.logger.debug(\"CAhandler._ca_load()\")\n        ca_key = self._ca_key_load()\n        (ca_cert, ca_id) = self._ca_cert_load()\n\n        self.logger.debug(\"CAhandler._ca_load() ended\")\n        return (ca_key, ca_cert, ca_id)\n\n    def _cdp_list_generate(self, cdp_string: str = None) -> List[str]:\n        \"\"\"generate cdp list\"\"\"\n        self.logger.debug(\"CAhandler._cdp_list_generate()\")\n\n        cdp_list = []\n        if cdp_string:\n            for ele in cdp_string.split(\",\"):\n                cdp_list.append(\n                    x509.DistributionPoint(\n                        [x509.UniformResourceIdentifier(ele.strip())],\n                        crl_issuer=None,\n                        reasons=None,\n                        relative_name=None,\n                    )\n                )\n\n        self.logger.debug(\"CAhandler._cdp_list_generate() ended\")\n        return cdp_list\n\n    def _cert_insert(self, cert_dic: Dict[str, str] = None) -> int:\n        \"\"\"insert new entry to request table\"\"\"\n        self.logger.debug(\"CAhandler._cert_insert()\")\n\n        row_id = None\n        if cert_dic:\n            if all(\n                key in cert_dic\n                for key in (\n                    \"item\",\n                    \"serial\",\n                    \"issuer\",\n                    \"ca\",\n                    \"cert\",\n                    \"iss_hash\",\n                    \"hash\",\n                )\n            ):\n                # pylint: disable=R0916\n                if (\n                    isinstance(cert_dic[\"item\"], int)\n                    and isinstance(cert_dic[\"issuer\"], int)\n                    and isinstance(cert_dic[\"ca\"], int)\n                    and isinstance(cert_dic[\"iss_hash\"], int)\n                    and isinstance(cert_dic[\"iss_hash\"], int)\n                    and isinstance(cert_dic[\"hash\"], int)\n                ):\n                    self._db_open()\n                    self.cursor.execute(\n                        \"\"\"INSERT INTO CERTS(item, serial, issuer, ca, cert, hash, iss_hash) VALUES(:item, :serial, :issuer, :ca, :cert, :hash, :iss_hash)\"\"\",\n                        cert_dic,\n                    )\n                    row_id = self.cursor.lastrowid\n                    self._db_close()\n                else:\n                    self.logger.error(\n                        \"Certificate insert aborted due to wrong datatypes: %s\",\n                        cert_dic,\n                    )\n            else:\n                self.logger.error(\n                    \"Certificate insert aborted due to incomplete dataset: %s\", cert_dic\n                )\n        else:\n            self.logger.error(\"Certificate insert aborted: dataset is empty\")\n\n        self.logger.debug(\"CAhandler._cert_insert() ended with row_id: %s\", row_id)\n        return row_id\n\n    def _cert_search(self, column: str, value: str) -> Dict[str, str]:\n        \"\"\"load ca key from database\"\"\"\n        self.logger.debug(\"CAhandler._cert_search({%s:%s)\", column, value)\n\n        if not self._identifier_check(\"items\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"items\")\n            return {}\n\n        # query database for key\n        self._db_open()\n        pre_statement = f\"\"\"SELECT * from items WHERE type == 3 and {column} LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [value])\n\n        cert_result = {}\n        try:\n            item_result = dict_from_row(self.cursor.fetchone())\n        except Exception:\n            self.logger.error(\n                \"Certificate item search in database failed: %s\", self.cursor.fetchone()\n            )\n            item_result = {}\n\n        if item_result:\n            item_id = item_result[\"id\"]\n            pre_statement = \"\"\"SELECT * from certs WHERE item LIKE ?\"\"\"\n            self.cursor.execute(pre_statement, [item_id])\n            try:\n                cert_result = dict_from_row(self.cursor.fetchone())\n            except Exception:\n                self.logger.error(\n                    \"Certificate search in database failed for item: %s\", item_id\n                )\n\n        self._db_close()\n        self.logger.debug(\"CAhandler._cert_search() ended\")\n        return cert_result\n\n    def _cert_subject_generate(\n        self, req: object, request_name: str, dn_dic: Dict[str, str] = None\n    ) -> str:\n        \"\"\"set subject\"\"\"\n        self.logger.debug(\"CAhandler._cert_subject_generate()\")\n\n        if not bool(req.subject):\n            self.logger.info(\"Rewrite CN to %s\", request_name)\n            subject = x509.Name(\n                [x509.NameAttribute(x509.NameOID.COMMON_NAME, request_name)]\n            )\n        else:\n            subject = req.subject\n\n        if dn_dic:\n            # modify subject according to template\n            subject = self._subject_modify(subject, dn_dic)\n\n        self.logger.debug(\"CAhandler._cert_subject_generate() ended\")\n        return subject\n\n    def _cert_sign(\n        self, csr: str, request_name: str, ca_key: object, ca_cert: object, ca_id: int\n    ) -> Tuple[str, str]:  # pylint: disable=R0913\n        self.logger.debug(\"Certificate._cert_sign()\")\n\n        if self.enrollment_config_log:\n            self.enrollment_config_log_skip_list.extend([\"dbs\", \"passphrase\"])\n            enrollment_config_log(\n                self.logger, self, self.enrollment_config_log_skip_list\n            )\n\n        # load template if configured\n        if self.template_name:\n            (dn_dic, template_dic) = self._template_load()\n        else:\n            dn_dic = {}\n            template_dic = {}\n\n        # creating a rest from CSR\n        req = x509.load_pem_x509_csr(convert_string_to_byte(csr), default_backend())\n\n        # set cert_validity\n        if \"validity\" in template_dic:\n            self.logger.debug(\n                \"Take validity from template: %s\", template_dic[\"validity\"]\n            )\n            # take validity from template\n            cert_validity = template_dic[\"validity\"]\n        else:\n            cert_validity = self.cert_validity_days\n\n        # create object for certificate\n        builder = x509.CertificateBuilder()\n\n        # set not valid before\n        builder = builder.not_valid_before(datetime.datetime.now(datetime.timezone.utc))\n        builder = builder.not_valid_after(\n            datetime.datetime.now(datetime.timezone.utc)\n            + datetime.timedelta(days=cert_validity)\n        )\n        builder = builder.issuer_name(ca_cert.subject)\n        builder = builder.serial_number(uuid.uuid4().int & (1 << 63) - 1)\n        builder = builder.public_key(req.public_key())\n\n        # get extension list from CSR\n        csr_extensions_list = req.extensions\n        extension_list = self._extension_list_generate(\n            template_dic, req, ca_cert, csr_extensions_list\n        )\n\n        # add extensions (copy from CSR and take the ones we constructed)\n        for extension in extension_list:\n            builder = builder.add_extension(\n                extension[\"name\"], critical=extension[\"critical\"]\n            )\n\n        # get subject and set to builder\n        builder = builder.subject_name(\n            self._cert_subject_generate(req, request_name, dn_dic)\n        )\n\n        # sign certificate\n        cert = builder.sign(\n            private_key=ca_key, algorithm=hashes.SHA256(), backend=default_backend()\n        )\n\n        # get serial\n        serial = cert.serial_number\n        # get hsshes\n        issuer_subject_hash = self._subject_name_hash_get(ca_cert)\n        cert_subject_hash = self._subject_name_hash_get(cert)\n\n        # store certificate\n        self._store_cert(\n            ca_id,\n            request_name,\n            f\"{serial:X}\",\n            convert_byte_to_string(\n                b64_encode(self.logger, cert.public_bytes(serialization.Encoding.DER))\n            ),\n            cert_subject_hash,\n            issuer_subject_hash,\n        )\n\n        cert_bundle = self._pemcertchain_generate(\n            convert_byte_to_string(cert.public_bytes(serialization.Encoding.PEM)),\n            convert_byte_to_string(ca_cert.public_bytes(serialization.Encoding.PEM)),\n        )\n        cert_raw = convert_byte_to_string(\n            b64_encode(self.logger, cert.public_bytes(serialization.Encoding.DER))\n        )\n\n        self.logger.debug(\"Certificate._cert_sign() ended.\")\n        return (cert_bundle, cert_raw)\n\n    def _columnnames_get(self, table: str) -> List[str]:\n        \"\"\"get columns of a table\"\"\"\n        self.logger.debug(\"CAhandler.columns_get(%s)\", table)\n\n        self._db_open()\n        pre_statement = f\"SELECT * from {table}\"\n        self.cursor.execute(pre_statement)\n        result = [column[0] for column in self.cursor.description]\n        self._db_close()\n\n        self.logger.debug(\n            \"CAhandler.columns_get() ended with: %s elements\", len(result)\n        )\n        return result\n\n    def _config_check(self) -> str:\n        \"\"\"check config for consitency\"\"\"\n        self.logger.debug(\"CAhandler._config_check()\")\n        error = None\n\n        if self.xdb_file:\n            if not os.path.exists(self.xdb_file):\n                error = f\"xdb_file {self.xdb_file} does not exist\"\n                self.xdb_file = None\n        else:\n            error = \"xdb_file must be specified in config file\"\n\n        if not error and not self.issuing_ca_name:\n            error = \"issuing_ca_name must be set in config file\"\n\n        if error:\n            self.logger.debug(\"CAhandler config error: %s\", error)\n\n        if not self.issuing_ca_key:\n            self.logger.debug(\n                \"use self.issuing_ca_name as self.issuing_ca_key: %s\",\n                self.issuing_ca_name,\n            )\n            self.issuing_ca_key = self.issuing_ca_name\n\n        self.logger.debug(\"CAhandler._config_check() ended\")\n        return error\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"CAhandler\")\n\n        if \"CAhandler\" in config_dic:\n            self.xdb_file = config_dic.get(\n                \"CAhandler\", \"xdb_file\", fallback=self.xdb_file\n            )\n            self.xdb_permission = config_dic.get(\n                \"CAhandler\", \"xdb_permission\", fallback=self.xdb_permission\n            )\n            self.issuing_ca_name = config_dic.get(\n                \"CAhandler\", \"issuing_ca_name\", fallback=self.issuing_ca_name\n            )\n            self.issuing_ca_key = config_dic.get(\n                \"CAhandler\", \"issuing_ca_key\", fallback=self.issuing_ca_key\n            )\n            self.template_name = config_dic.get(\n                \"CAhandler\", \"template_name\", fallback=self.template_name\n            )\n\n        if \"passphrase_variable\" in config_dic[\"CAhandler\"]:\n            try:\n                self.passphrase = os.environ[\n                    config_dic.get(\"CAhandler\", \"passphrase_variable\")\n                ]\n            except Exception as err:\n                self.logger.error(\n                    \"Could not load passphrase_variable:%s\",\n                    err,\n                )\n\n        if \"passphrase\" in config_dic[\"CAhandler\"]:\n            # overwrite passphrase specified in variable\n            if self.passphrase:\n                self.logger.info(\"Overwrite passphrase_variable\")\n            self.passphrase = config_dic.get(\"CAhandler\", \"passphrase\")\n\n        if \"ca_cert_chain_list\" in config_dic[\"CAhandler\"]:\n            try:\n                self.ca_cert_chain_list = json.loads(\n                    config_dic.get(\"CAhandler\", \"ca_cert_chain_list\")\n                )\n            except Exception:\n                self.logger.error('Parameter \"ca_cert_chain_list\" cannot be loaded')\n\n        # load profiling\n        self.eab_profiling, self.eab_handler = config_eab_profile_load(\n            self.logger, config_dic\n        )\n\n        # load profiles\n        self.profiles = config_profile_load(self.logger, config_dic)\n\n        # load header info\n        self.header_info_field = config_headerinfo_load(self.logger, config_dic)\n\n        # load enrollment config log\n        (\n            self.enrollment_config_log,\n            self.enrollment_config_log_skip_list,\n        ) = config_enroll_config_log_load(self.logger, config_dic)\n\n    def _csr_import(self, csr, request_name):\n        \"\"\"check existance of csr and load into db\"\"\"\n        self.logger.debug(\"CAhandler._csr_import()\")\n\n        csr_info = self._csr_search(\"request\", csr)\n\n        if not csr_info:\n\n            # csr does not exist in db - lets import it\n            insert_date = uts_to_date_utc(uts_now(), DEFAULT_DATE_FORMAT)\n            item_dic = {\n                \"type\": 2,\n                \"comment\": \"from acme2certifier\",\n                \"source\": 2,\n                \"date\": insert_date,\n                \"name\": request_name,\n            }\n            row_id = self._item_insert(item_dic)\n\n            # insert csr\n            csr_info = {\"item\": row_id, \"signed\": 1, \"request\": csr}\n            self._csr_insert(csr_info)\n\n        self.logger.debug(\"CAhandler._csr_import() ended\")\n        return csr_info\n\n    def _csr_insert(self, csr_dic: Dict[str, str] = None) -> int:\n        \"\"\"insert new entry to request table\"\"\"\n        self.logger.debug(\"CAhandler._csr_insert()\")\n        row_id = None\n        if csr_dic:\n            if all(key in csr_dic for key in (\"item\", \"signed\", \"request\")):\n                # item and signed must be integer\n                if isinstance(csr_dic[\"item\"], int) and isinstance(\n                    csr_dic[\"signed\"], int\n                ):\n                    self._db_open()\n                    self.cursor.execute(\n                        \"\"\"INSERT INTO REQUESTS(item, signed, request) VALUES(:item, :signed, :request)\"\"\",\n                        csr_dic,\n                    )\n                    row_id = self.cursor.lastrowid\n                    self._db_close()\n                else:\n                    self.logger.error(\n                        \"CSR insert aborted due to wrong datatypes: %s\", csr_dic\n                    )\n            else:\n                self.logger.error(\n                    \"CSR insert aborted due to incomplete dataset: %s\", csr_dic\n                )\n        else:\n            self.logger.error(\"CSR insert aborted: dataset is empty\")\n\n        self.logger.debug(\"CAhandler._csr_insert() ended with row_id: %s\", row_id)\n        return row_id\n\n    def _csr_search(self, column: str, value: str) -> Dict[str, str]:\n        \"\"\"load ca key from database\"\"\"\n        self.logger.debug(\"CAhandler._csr_search()\")\n\n        if not self._identifier_check(\"view_requests\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"view_requests\")\n            return {}\n\n        # query database for key\n        self._db_open()\n        pre_statement = f\"\"\"SELECT * from view_requests WHERE {column} LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [value])\n\n        try:\n            db_result = dict_from_row(self.cursor.fetchone())\n        except Exception:\n            db_result = {}\n        self._db_close()\n        self.logger.debug(\"CAhandler._csr_search() ended with: %s\", bool(db_result))\n        return db_result\n\n    def _db_check(self):\n        \"\"\"do various checks on database\"\"\"\n        self.logger.debug(\"CAhandler._db_check()\")\n        error = None\n\n        st = os.stat(self.xdb_file)\n        oct_perm = oct(st.st_mode)[-3:]\n\n        # test open failure\n        if not os.access(self.xdb_file, os.R_OK):\n            error = f\"xdb_file {self.xdb_file} is not readable\"\n        elif not os.access(self.xdb_file, os.W_OK):\n            error = f\"xdb_file {self.xdb_file} is not writeable\"\n        # warns if permissions are to wide\n        elif (\n            int(oct_perm[0]) > int(self.xdb_permission[0])\n            or int(oct_perm[1]) > int(self.xdb_permission[1])\n            or int(oct_perm[2]) > int(self.xdb_permission[2])\n        ):\n            self.logger.warning(\n                \"File permissions %s for '%s' are too permissive. Should be %s.\",\n                oct_perm,\n                self.xdb_file,\n                self.xdb_permission,\n            )\n\n        # validates passphrase against database\n        if not error:\n            ca_key = self._ca_key_load()\n            if not ca_key:\n                error = \"ca_key_load failed. PLease check passphrase\"\n\n        self.logger.debug(\"CAhandler._db_check() ended with: %s\", error)\n        return error\n\n    def _db_open(self):\n        \"\"\"opens db and sets cursor\"\"\"\n        # pylint: disable=W0201\n        self.dbs = sqlite3.connect(self.xdb_file)\n        self.dbs.row_factory = sqlite3.Row\n        # pylint: disable=W0201\n        self.cursor = self.dbs.cursor()\n\n    def _db_close(self):\n        \"\"\"commit and close\"\"\"\n        # self.logger.debug('DBStore._db_close()')\n        self.dbs.commit()\n        self.dbs.close()\n        # self.logger.debug('DBStore._db_close() ended')\n\n    def _extended_keyusage_generate(\n        self, template_dic: Dict[str, str], _csr_extensions_dic: Dict[str, str] = None\n    ) -> Tuple[bool, List[str]]:\n        \"\"\"set generate extended key usage extenstion\"\"\"\n        self.logger.debug(\"CAhandler._extended_keyusage_generate()\")\n\n        eku_list = []\n        if \"eKeyUse\" in template_dic:\n            # eku included in tempalate\n            eku_mapping_dic = {\n                \"clientAuth\": ExtendedKeyUsageOID.CLIENT_AUTH,\n                \"serverAuth\": ExtendedKeyUsageOID.SERVER_AUTH,\n                \"codeSigning\": ExtendedKeyUsageOID.CODE_SIGNING,\n                \"emailProtection\": ExtendedKeyUsageOID.EMAIL_PROTECTION,\n                \"timeStamping\": ExtendedKeyUsageOID.TIME_STAMPING,\n                \"OCSPSigning\": ExtendedKeyUsageOID.OCSP_SIGNING,\n                \"eKeyUse\": \"eKeyUse\",  # this is just for testing\n            }\n            # backwards compatibility with cryptography module coming Ubuntu 22.04\n            if hasattr(ExtendedKeyUsageOID, \"KERBEROS_PKINIT_KDC\"):\n                eku_mapping_dic[\"pkInitKDC\"] = ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC\n\n            if \"ekuCritical\" in template_dic:\n                try:\n                    ekuc = bool(int(template_dic[\"ekuCritical\"]))\n                except Exception:\n                    self.logger.error(\n                        \"Failed to convert EKU critical flag to int, defaulting to False\"\n                    )\n                    ekuc = False\n            else:\n                ekuc = False\n\n            for ele in template_dic[\"eKeyUse\"].split(\", \"):\n                if ele in eku_mapping_dic:\n                    eku_list.append(eku_mapping_dic[ele])\n\n        else:\n            # neither extension nor template\n            eku_list = None\n            ekuc = False\n\n        return (ekuc, eku_list)\n\n    def _extension_list_default(self, ca_cert: str = None, cert: str = None):\n        \"\"\"set default extension list\"\"\"\n        self.logger.debug(\"CAhandler._extension_list_default()\")\n\n        extension_list = [\n            {\"name\": BasicConstraints(ca=False, path_length=None), \"critical\": True},\n            {\n                \"name\": KeyUsage(\n                    digital_signature=True,\n                    key_encipherment=True,\n                    content_commitment=False,\n                    data_encipherment=False,\n                    key_agreement=False,\n                    key_cert_sign=False,\n                    crl_sign=False,\n                    encipher_only=False,\n                    decipher_only=False,\n                ),\n                \"critical\": True,\n            },\n            {\n                \"name\": ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]),\n                \"critical\": False,\n            },\n        ]\n        if cert:\n            extension_list.append(\n                {\n                    \"name\": SubjectKeyIdentifier.from_public_key(cert.public_key()),\n                    \"critical\": False,\n                },\n            )\n        if ca_cert:\n            extension_list.append(\n                {\n                    \"name\": AuthorityKeyIdentifier.from_issuer_public_key(\n                        ca_cert.public_key()\n                    ),\n                    \"critical\": False,\n                }\n            )\n\n        self.logger.debug(\"CAhandler._extension_list_default() ended\")\n        return extension_list\n\n    def _extension_list_generate(\n        self,\n        template_dic: Dict[str, str],\n        cert: str,\n        ca_cert: str,\n        csr_extensions_list: List[str] = None,\n    ) -> List[str]:\n        \"\"\"set extension list\"\"\"\n        self.logger.debug(\"CAhandler._extension_list_generate()\")\n\n        csr_extensions_dic = {}\n        if csr_extensions_list:\n            for ext in csr_extensions_list:\n                csr_extensions_dic[\n                    convert_byte_to_string(ext.oid._name)\n                ] = ext  # pylint: disable=W0212\n\n        if template_dic:\n            # prcoess xca template\n            extension_list = self._xca_template_process(\n                template_dic, csr_extensions_dic, cert, ca_cert\n            )\n        else:\n            extension_list = self._extension_list_default(ca_cert, cert)\n\n        # add subjectAltName(s)\n        if \"subjectAltName\" in csr_extensions_dic:\n            # pylint: disable=C2801\n            self.logger.debug(\n                \"CAhandler._extension_list_generate(): adding subAltNames: %s\",\n                csr_extensions_dic[\"subjectAltName\"].__str__(),\n            )\n            extension_list.append(\n                {\n                    \"name\": SubjectAlternativeName(\n                        csr_extensions_dic[\"subjectAltName\"].value\n                    ),\n                    \"critical\": False,\n                }\n            )\n\n        self.logger.debug(\"CAhandler._extension_list_generate() ended\")\n        return extension_list\n\n    def _identifier_check(self, table: str, identifier: str) -> bool:\n        \"\"\"check if identifier is in table\"\"\"\n        self.logger.debug(\"CAhandler._identifier_check(%s, %s)\", identifier, table)\n        if \".\" in identifier:\n            # we have a table.column name\n            table, identifier = identifier.split(\".\", 1)\n            self.logger.debug(\n                \"CAhandler._identifier_check(): modified table/identifier to %s/%s\",\n                table,\n                identifier,\n            )\n        elif \"__\" in identifier:\n            # we have a table__column name\n            table, identifier = identifier.split(\"__\", 1)\n            self.logger.debug(\n                \"CAhandler._identifier_check(): modified table/identifier to %s/%s\",\n                table,\n                identifier,\n            )\n        if self._table_check(table):\n            columnname_list = self._columnnames_get(table)\n            result = True if identifier in columnname_list else False\n        else:\n            self.logger.warning(\"Table '%s' does not exist in the database.\", table)\n            result = False\n\n        self.logger.debug(\"CAhandler._identifier_check() ended with: %s\", result)\n        return result\n\n    def _item_insert(self, item_dic: Dict[str, str] = None) -> int:\n        \"\"\"insert new entry to item_table\"\"\"\n        self.logger.debug(\"CAhandler._item_insert()\")\n        row_id = None\n        # insert\n        if item_dic:\n            if all(\n                key in item_dic for key in (\"name\", \"type\", \"source\", \"date\", \"comment\")\n            ):\n                if isinstance(item_dic[\"type\"], int) and isinstance(\n                    item_dic[\"source\"], int\n                ):\n                    self._db_open()\n                    self.cursor.execute(\n                        \"\"\"INSERT INTO ITEMS(name, type, source, date, comment) VALUES(:name, :type, :source, :date, :comment)\"\"\",\n                        item_dic,\n                    )\n                    row_id = self.cursor.lastrowid\n                    # update stamp field\n                    data_dic = {\"stamp\": row_id}\n                    self.cursor.execute(\n                        \"\"\"UPDATE ITEMS SET stamp = :stamp WHERE id = :stamp\"\"\",\n                        data_dic,\n                    )\n                    self._db_close()\n                else:\n                    self.logger.error(\n                        \"Item insert aborted due to wrong datatypes: %s\", item_dic\n                    )\n            else:\n                self.logger.error(\n                    \"Item insert aborted due to incomplete dataset: %s\", item_dic\n                )\n        else:\n            self.logger.error(\"Item insert aborted: dataset is empty\")\n\n        self.logger.debug(\"CAhandler._item_insert() ended with row_id: %s\", row_id)\n        return row_id\n\n    def _keyusage_generate(\n        self, template_dic: Dict[str, str], _csr_extensions_dic: Dict[str, str] = None\n    ) -> Tuple[bool, Dict[str, str]]:\n        \"\"\"set generate key usage extenstion\"\"\"\n        self.logger.debug(\"CAhandler._keyusage_generate()\")\n\n        if \"keyUse\" in template_dic:\n            if \"kuCritical\" in template_dic:\n                try:\n                    kuc = bool(int(template_dic[\"kuCritical\"]))\n                except Exception:\n                    kuc = False\n            else:\n                kuc = False\n            kup = template_dic[\"keyUse\"]\n        else:\n            kuc = False\n            kup = 0\n\n        # generate key-usage extension\n        ku_dic = self._kue_generate(kup)\n\n        return (kuc, ku_dic)\n\n    def _kue_generate(self, kuval: int = 0, ku_csr: str = None) -> Dict[str, str]:\n        \"\"\"set generate key usage extension\"\"\"\n        self.logger.debug(\"CAhandler._kue_generate()\")\n\n        # convert keyusage value from template\n        if kuval:\n            try:\n                kuval = int(kuval)\n            except Exception:\n                self.logger.error(\n                    \"Keyusage value conversion to int failed, defaulting to 0\"\n                )\n                kuval = 0\n\n        if kuval:\n            # we have a key-usage value from template\n            self.logger.debug(\"Generate KeyUsage Extension with data from template\")\n            ku_dic = self._ku_dict_generate(kuval)\n        elif ku_csr:\n            # no data from template but data from csr\n            self.logger.debug(\"Generate KeyUsage Extension with data from csr\")\n            ku_dic = ku_csr\n        else:\n            # no data from template no data from csr - default (23)\n            self.logger.debug(\"Generate KeyUsage Extension with value 23\")\n            ku_dic = self._ku_dict_generate(23)\n\n        self.logger.debug(\"CAhandler._kue_generate() ended with: %s\", ku_dic)\n        return ku_dic\n\n    def _ku_dict_generate(self, kuval: int = 0) -> Dict[str, str]:\n        self.logger.debug(\"CAhandler._ku_dict_generate(%s)\", kuval)\n\n        # generate and reverse key_usage_list\n        key_usage_list = [\n            \"digital_signature\",\n            \"content_commitment\",\n            \"key_encipherment\",\n            \"data_encipherment\",\n            \"key_agreement\",\n            \"key_cert_sign\",\n            \"crl_sign\",\n            \"encipher_only\",\n            \"decipher_only\",\n        ]\n        key_usage_dic = {\n            \"digital_signature\": False,\n            \"content_commitment\": False,\n            \"key_encipherment\": False,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n\n        kubin = f\"{kuval:b}\"[::-1]\n        for idx, ele in enumerate(kubin):\n            if ele == \"1\":\n                key_usage_dic[key_usage_list[idx]] = True\n\n        self.logger.debug(\"CAhandler._ku_dict_generate() ended with: %s\", key_usage_dic)\n        return key_usage_dic\n\n    def _pemcertchain_generate(self, ee_cert: str, issuer_cert: str = None) -> str:\n        \"\"\"build pem chain\"\"\"\n        self.logger.debug(\"CAhandler._pemcertchain_generate()\")\n\n        if issuer_cert:\n            pem_chain = f\"{ee_cert}{issuer_cert}\"\n        else:\n            pem_chain = ee_cert\n\n        for cert in self.ca_cert_chain_list:\n            cert_dic = self._cert_search(\"items.name\", cert)\n            if cert_dic and \"cert\" in cert_dic:\n                ca_cert = x509.load_der_x509_certificate(\n                    b64_decode(self.logger, cert_dic[\"cert\"]), backend=default_backend()\n                )\n                pem_chain = f\"{pem_chain}{convert_byte_to_string(ca_cert.public_bytes(serialization.Encoding.PEM))}\"\n\n        self.logger.debug(\"CAhandler._pemcertchain_generate() ended\")\n        return pem_chain\n\n    def _requestname_get(self, csr: str = None) -> str:\n        \"\"\"get request name\"\"\"\n        self.logger.debug(\"CAhandler._requestname_get()\")\n\n        # try to get cn for a name in database\n        request_name = csr_cn_get(self.logger, csr)\n        if not request_name:\n            san_list = csr_san_get(self.logger, csr)\n            try:\n                (_identifiier, request_name,) = san_list[\n                    0\n                ].split(\":\")\n            except Exception:\n                self.logger.error(\n                    \"Failed to split SAN from CSR subjectAltName: %s\", san_list\n                )\n\n        self.logger.debug(\"CAhandler._requestname_get() ended with: %s\", request_name)\n        return request_name\n\n    def _revocation_insert(self, rev_dic: Dict[str, str] = None) -> int:\n        \"\"\"insert new entry to into revocation_table\"\"\"\n        self.logger.debug(\"CAhandler._revocation_insert()\")\n        row_id = None\n        # insert\n        if rev_dic:\n            if all(\n                key in rev_dic\n                for key in (\"caID\", \"serial\", \"date\", \"invaldate\", \"reasonBit\")\n            ):\n                if isinstance(rev_dic[\"caID\"], int) and isinstance(\n                    rev_dic[\"reasonBit\"], int\n                ):\n                    self._db_open()\n                    self.cursor.execute(\n                        \"\"\"INSERT INTO REVOCATIONS(caID, serial, date, invaldate, reasonBit) VALUES(:caID, :serial, :date, :invaldate, :reasonBit)\"\"\",\n                        rev_dic,\n                    )\n                    row_id = self.cursor.lastrowid\n                    self._db_close()\n                else:\n                    self.logger.error(\n                        \"Revocation insert aborted due to wrong datatypes: %s\", rev_dic\n                    )\n            else:\n                self.logger.error(\n                    \"Revocation insert aborted due to incomplete dataset: %s\", rev_dic\n                )\n        else:\n            self.logger.error(\"Revocation insert aborted: dataset is empty\")\n\n        self.logger.debug(\n            \"CAhandler._revocation_insert() ended with row_id: %s\", row_id\n        )\n        return row_id\n\n    def _revocation_check(\n        self, serial: str, ca_id: int, err_msg_dic: Dict[str, str] = None\n    ) -> Tuple[int, str, str]:\n        self.logger.debug(\"CAhandler.revoke(%s/%s)\", serial, ca_id)\n\n        # check if certificate has alreay been revoked:\n        if not self._revocation_search(\"serial\", serial):\n            rev_dic = {\n                \"caID\": ca_id,\n                \"serial\": serial,\n                \"date\": uts_to_date_utc(uts_now(), DEFAULT_DATE_FORMAT),\n                \"invaldate\": uts_to_date_utc(uts_now(), DEFAULT_DATE_FORMAT),\n                \"reasonBit\": 0,\n            }\n            row_id = self._revocation_insert(rev_dic)\n            if row_id:\n                code = 200\n                message = None\n                detail = None\n            else:\n                code = 500\n                message = err_msg_dic[\"serverinternal\"]\n                detail = \"database update failed\"\n        else:\n            code = 400\n            message = err_msg_dic[\"alreadyrevoked\"]\n            detail = \"Certificate has already been revoked\"\n\n        self.logger.debug(\"CAhandler.revoke() ended with: %s\", code)\n        return (code, message, detail)\n\n    def _revocation_search(self, column: str, value: str) -> Dict[str, str]:\n        \"\"\"load ca key from database\"\"\"\n        self.logger.debug(\"CAhandler._revocation_search()\")\n\n        if not self._identifier_check(\"revocations\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"revocations\")\n            return {}\n        # query database for key\n        self._db_open()\n        pre_statement = f\"\"\"SELECT * from revocations WHERE {column} LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [value])\n\n        try:\n            db_result = dict_from_row(self.cursor.fetchone())\n        except Exception:\n            db_result = {}\n        self._db_close()\n        self.logger.debug(\"CAhandler._revocation_search() ended\")\n        return db_result\n\n    # pylint: disable=R0913\n    def _store_cert(\n        self,\n        ca_id: int,\n        cert_name: str,\n        serial: str,\n        cert: str,\n        name_hash: str,\n        issuer_hash: str,\n    ) -> int:\n        \"\"\"store certificate to database\"\"\"\n        self.logger.debug(\"CAhandler._store_cert()\")\n\n        # insert certificate into item table\n        insert_date = uts_to_date_utc(uts_now(), DEFAULT_DATE_FORMAT)\n        item_dic = {\n            \"type\": 3,\n            \"comment\": \"from acme2certifier\",\n            \"source\": 2,\n            \"date\": insert_date,\n            \"name\": cert_name,\n        }\n        row_id = self._item_insert(item_dic)\n        # insert certificate to cert table\n        cert_dic = {\n            \"item\": row_id,\n            \"serial\": serial,\n            \"issuer\": ca_id,\n            \"ca\": 0,\n            \"cert\": cert,\n            \"iss_hash\": issuer_hash,\n            \"hash\": name_hash,\n        }\n        _row_id = self._cert_insert(cert_dic)  # lgtm [py/unused-local-variable]\n\n        self.logger.debug(\"CAhandler._store_cert() ended\")\n\n    def _stream_split(self, byte_stream: str = None) -> Tuple[str, str]:\n        \"\"\"split template in asn1 structure and utf_stream\"\"\"\n        self.logger.debug(\"CAhandler._stream_split()\")\n        asn1_stream = None\n        utf_stream = None\n\n        # convert to byte if not already done\n        byte_stream = convert_string_to_byte(byte_stream)\n\n        if byte_stream:\n            # search pattern\n            pos = byte_stream.find(b\"\\x00\\x00\\x00\\x0c\") + 4\n            if pos != 3:\n                # split file 3 bcs find returns -1 in case of no-match\n                asn1_stream = byte_stream[:pos]\n                utf_stream = byte_stream[pos:]\n\n        self.logger.debug(\n            \"CAhandler._stream_split() ended: %s:%s\",\n            bool(asn1_stream),\n            bool(utf_stream),\n        )\n        return (asn1_stream, utf_stream)\n\n    def _stub_func(self, parameter: str) -> str:\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"CAhandler._stub_func(%s)\", parameter)\n        self.logger.debug(\"CAhandler._stub_func() ended\")\n        return parameter\n\n    def _subject_name_hash_get(self, cert: str = None) -> int:\n        \"\"\"get subject name hash\"\"\"\n        self.logger.debug(\"CAhandler._subject_name_hash_get()\")\n\n        pyopenssl_cert = pyossslcrypto.X509.from_cryptography(cert)\n        pyopenssl_subject_name_hash = pyopenssl_cert.subject_name_hash() & 0x7FFFFFFF\n\n        return pyopenssl_subject_name_hash\n\n    def _subject_modify(self, subject: str, dn_dic: Dict[str, str] = None) -> str:\n        \"\"\"modify subject name\"\"\"\n        self.logger.debug(\"CAhandler._subject_modify()\")\n\n        subject_name_list = []\n\n        if \"organizationalUnitName\" in dn_dic and dn_dic[\"organizationalUnitName\"]:\n            self.logger.info(\"Rewrite OU to %s\", dn_dic[\"organizationalUnitName\"])\n            subject_name_list.append(\n                x509.NameAttribute(\n                    x509.NameOID.ORGANIZATIONAL_UNIT_NAME,\n                    dn_dic[\"organizationalUnitName\"],\n                )\n            )\n        if \"organizationName\" in dn_dic and dn_dic[\"organizationName\"]:\n            self.logger.info(\"Rewrite O to %s\", dn_dic[\"organizationName\"])\n            subject_name_list.append(\n                x509.NameAttribute(\n                    x509.NameOID.ORGANIZATION_NAME, dn_dic[\"organizationName\"]\n                )\n            )\n        if \"localityName\" in dn_dic and dn_dic[\"localityName\"]:\n            self.logger.info(\"Rewrite L to %s\", dn_dic[\"localityName\"])\n            subject_name_list.append(\n                x509.NameAttribute(x509.NameOID.LOCALITY_NAME, dn_dic[\"localityName\"])\n            )\n        if \"stateOrProvinceName\" in dn_dic and dn_dic[\"stateOrProvinceName\"]:\n            self.logger.info(\"Rewrite ST to %s\", dn_dic[\"stateOrProvinceName\"])\n            subject_name_list.append(\n                x509.NameAttribute(\n                    x509.NameOID.STATE_OR_PROVINCE_NAME, dn_dic[\"stateOrProvinceName\"]\n                )\n            )\n        if \"countryName\" in dn_dic and dn_dic[\"countryName\"]:\n            self.logger.info(\"Rewrite C to %s\", dn_dic[\"countryName\"])\n            subject_name_list.append(\n                x509.NameAttribute(x509.NameOID.COUNTRY_NAME, dn_dic[\"countryName\"])\n            )\n\n        if subject_name_list:\n            subject = x509.Name([*subject, *subject_name_list])\n\n        self.logger.debug(\"CAhandler._subject_modify() ended\")\n        return subject\n\n    def _table_check(self, table: str) -> bool:\n        \"\"\"get all tables in db\"\"\"\n        self.logger.debug(\"DBStore.tables_get()\")\n        self._db_open()\n        pre_statement = (\n            \"SELECT name FROM sqlite_master WHERE type='table' or type == 'view'\"\n        )\n        self.cursor.execute(pre_statement)\n        tables_list = [row[0] for row in self.cursor.fetchall()]\n        self._db_close()\n        result = True if table in tables_list else False\n        self.logger.debug(\"DBStore._table_check() ended with: %s\", result)\n        return result\n\n    def _template_load(self) -> Tuple[Dict[str, str], Dict[str, str]]:\n        \"\"\"load template from database\"\"\"\n        self.logger.debug(\"CAhandler._template_load(%s)\", self.template_name)\n        # query database for template\n        self._db_open()\n        pre_statement = \"\"\"SELECT * from view_templates WHERE name LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [self.template_name])\n        try:\n            db_result = dict_from_row(self.cursor.fetchone())\n        except Exception:\n            self.logger.error(\"template lookup failed: %s\", self.cursor.fetchone())\n            db_result = {}\n\n        # parse template\n        dn_dic = {}\n        template_dic = {}\n        if \"template\" in db_result:\n            byte_stream = b64_decode(self.logger, db_result[\"template\"])\n            (dn_dic, template_dic) = self._template_parse(byte_stream)\n        self._db_close()\n        self.logger.debug(\"CAhandler._template_load() ended\")\n\n        return (dn_dic, template_dic)\n\n    def _template_parse(\n        self, byte_string: str = None\n    ) -> Tuple[Dict[str, str], Dict[str, str]]:\n        \"\"\"process template\"\"\"\n        self.logger.debug(\"CAhandler._template_parse()\")\n        (asn1_stream, utf_stream) = self._stream_split(byte_string)\n\n        dn_dic = {}\n        if asn1_stream:\n            dn_dic = self._asn1_stream_parse(asn1_stream)\n\n        template_dic = {}\n        if utf_stream:\n            template_dic = self._utf_stream_parse(utf_stream)\n            if template_dic:\n                # replace '' with None\n                template_dic = {\n                    k: None if not v else v for k, v in template_dic.items()\n                }\n\n                template_dic[\"validity\"] = self._validity_calculate(template_dic)\n\n        self.logger.debug(\"CAhandler._template_parse() ended\")\n        return (dn_dic, template_dic)\n\n    def _utf_stream_parse(self, utf_stream: str = None) -> Dict[str, str]:\n        \"\"\"parse template information from utf_stream into dictitionary\"\"\"\n        self.logger.debug(\"CAhandler._utf_stream_parse()\")\n        template_dic = {}\n\n        if utf_stream:\n            stream_list = utf_stream.split(b\"\\x00\\x00\\x00\")\n\n            # iterate list and clean up parameter\n            parameter_list = []\n            for idx, ele in enumerate(stream_list):\n                ele = ele.replace(b\"\\x00\", b\"\")\n                if idx > 0:\n                    # strip the first character\n                    ele = ele[1:]\n                if ele == b\"eKeyUse\\xff\\xff\\xff\\xff\":\n                    self.logger.info(\n                        \"Hack to skip template with empty eku - maybe a bug in xca...\"\n                    )\n                else:\n                    parameter_list.append(ele.decode(\"utf-8\"))\n\n            if parameter_list:\n                if len(parameter_list) % 2 != 0:\n                    # remove last element from list if amount of list entries is uneven\n                    parameter_list.pop()\n                # convert list into a directory\n                template_dic = {\n                    item: parameter_list[index + 1]\n                    for index, item in enumerate(parameter_list)\n                    if index % 2 == 0\n                }\n\n        self.logger.debug(\"CAhandler._utf_stream_parse() ended: %s\", bool(template_dic))\n        return template_dic\n\n    def _validity_calculate(self, template_dic: Dict[str, str] = None) -> int:\n        \"\"\"calculate validity in days\"\"\"\n        self.logger.debug(\"CAhandler._validity_calculate()\")\n\n        cert_validity = 365\n        if \"validM\" in template_dic and \"validN\" in template_dic:\n            if template_dic[\"validM\"] == \"0\":\n                # validity in days\n                cert_validity = int(template_dic[\"validN\"])\n            elif template_dic[\"validM\"] == \"1\":\n                # validity in months\n                cert_validity = int(template_dic[\"validN\"]) * 30\n            elif template_dic[\"validM\"] == \"2\":\n                # validity in months\n                cert_validity = int(template_dic[\"validN\"]) * 365\n        else:\n            cert_validity = 365\n\n        self.logger.debug(\n            \"CAhandler._validity_calculate() ended with: %s\", cert_validity\n        )\n        return cert_validity\n\n    def _xca_template_process(\n        self,\n        template_dic: Dict[str, str],\n        csr_extensions_dic: Dict[str, str],\n        cert: str,\n        ca_cert: str,\n    ) -> List[str]:\n        \"\"\"add xca template\"\"\"\n        self.logger.debug(\"Certificate._xca_template_process()\")\n\n        extension_list = [\n            {\n                \"name\": SubjectKeyIdentifier.from_public_key(cert.public_key()),\n                \"critical\": False,\n            },\n            {\n                \"name\": AuthorityKeyIdentifier.from_issuer_public_key(\n                    ca_cert.public_key()\n                ),\n                \"critical\": False,\n            },\n        ]\n\n        # key_usage\n        (kuc, ku_dic) = self._keyusage_generate(template_dic, csr_extensions_dic)\n        extension_list.append({\"name\": KeyUsage(**ku_dic), \"critical\": kuc})\n\n        # extended key_usage\n        (ekuc, eku_list) = self._extended_keyusage_generate(\n            template_dic, csr_extensions_dic\n        )\n        if eku_list:\n            extension_list.append(\n                {\"name\": ExtendedKeyUsage(eku_list), \"critical\": ekuc}\n            )\n\n        # add cdp\n        if \"crlDist\" in template_dic and template_dic[\"crlDist\"]:\n            cdp_list = self._cdp_list_generate(template_dic[\"crlDist\"])\n            extension_list.append(\n                {\"name\": x509.CRLDistributionPoints(cdp_list), \"critical\": False}\n            )\n\n        # add basicConstraints\n        if \"ca\" in template_dic:\n            if \"bcCritical\" in template_dic:\n                try:\n                    bcc = bool(int(template_dic[\"bcCritical\"]))\n                except Exception:\n                    bcc = False\n            else:\n                bcc = False\n\n            if template_dic[\"ca\"] == \"1\":\n                extension_list.append(\n                    {\n                        \"name\": BasicConstraints(ca=True, path_length=None),\n                        \"critical\": bcc,\n                    }\n                )\n            elif template_dic[\"ca\"] == \"2\":\n                extension_list.append(\n                    {\n                        \"name\": BasicConstraints(ca=False, path_length=None),\n                        \"critical\": bcc,\n                    }\n                )\n\n        return extension_list\n\n    def handler_check(self):\n        \"\"\"check if handler is ready\"\"\"\n        self.logger.debug(\"CAhandler.check()\")\n\n        error = self._config_check()\n        if not error:\n            error = self._db_check()\n\n        self.logger.debug(\"CAhandler.check() ended with %s\", error)\n        return error\n\n    def enroll(self, csr: str = None) -> Tuple[str, str, str, str]:\n        \"\"\"enroll certificate\"\"\"\n        # pylint: disable=R0914, R0915\n        self.logger.debug(\"CAhandler.enroll()\")\n\n        cert_bundle = None\n        cert_raw = None\n        error = self._config_check()\n\n        if not error:\n            error = self._db_check()\n\n        # fmt: off\n        if not error:\n            error = eab_profile_header_info_check(self.logger, self, csr, \"template_name\")\n        # fmt: on\n\n        if not error:\n            request_name = self._requestname_get(csr)\n\n            if request_name:\n                # import CSR to database\n                _csr_info = self._csr_import(\n                    csr, request_name\n                )  # lgtm [py/unused-local-variable]\n\n                # prepare the CSR to be signed\n                csr = build_pem_file(\n                    self.logger, None, b64_url_recode(self.logger, csr), None, True\n                )\n\n                # load ca cert and key\n                (ca_key, ca_cert, ca_id) = self._ca_load()\n\n                if ca_key and ca_cert and ca_id:\n                    (cert_bundle, cert_raw) = self._cert_sign(\n                        csr, request_name, ca_key, ca_cert, ca_id\n                    )\n                else:\n                    error = \"ca lookup failed\"\n            else:\n                error = \"request_name lookup failed\"\n        self.logger.debug(\"Certificate.enroll() ended\")\n        return (error, cert_bundle, cert_raw, None)\n\n    def poll(\n        self, cert_name: str, poll_identifier: str, _csr: str\n    ) -> Tuple[str, str, str, str, bool]:\n        \"\"\"poll status of pending CSR and download certificates\"\"\"\n        self.logger.debug(\"CAhandler.poll()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        rejected = False\n        self._stub_func(cert_name)\n\n        self.logger.debug(\"CAhandler.poll() ended\")\n        return (error, cert_bundle, cert_raw, poll_identifier, rejected)\n\n    def revoke(\n        self, cert: str, _rev_reason: str = \"unspecified\", _rev_date: str = None\n    ) -> Tuple[int, str, str]:\n        \"\"\"revoke certificate\"\"\"\n        self.logger.debug(\"CAhandler.revoke()\")\n\n        err_msg_dic = error_dic_get(self.logger)\n\n        # modify handler configuration in case of eab profiling\n        if self.eab_profiling:\n            eab_profile_revocation_check(self.logger, self, cert)\n\n        if self.xdb_file:\n            # load ca cert and key\n            (_ca_key, _ca_cert, ca_id) = self._ca_load()\n\n            serial = cert_serial_get(self.logger, cert)\n            if serial:\n                serial = f\"{serial:X}\"\n\n            if ca_id and serial:\n                (code, message, detail) = self._revocation_check(\n                    serial, ca_id, err_msg_dic\n                )\n            else:\n                code = 500\n                message = err_msg_dic[\"serverinternal\"]\n                detail = \"certificate lookup failed\"\n        else:\n            code = 500\n            message = err_msg_dic[\"serverinternal\"]\n            detail = \"configuration error\"\n\n        self.logger.debug(\"Certificate.revoke() ended\")\n        return (code, message, detail)\n\n    def trigger(self, payload: str) -> Tuple[str, str, str]:\n        \"\"\"process trigger message and return certificate\"\"\"\n        self.logger.debug(\"CAhandler.trigger()\")\n\n        error = \"Method not implemented.\"\n        cert_bundle = None\n        cert_raw = None\n        self._stub_func(payload)\n\n        self.logger.debug(\"CAhandler.trigger() ended with error: %s\", error)\n        return (error, cert_bundle, cert_raw)\n"
  },
  {
    "path": "examples/db_handler/__init__.py",
    "content": ""
  },
  {
    "path": "examples/db_handler/django_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"django handler for acme2certifier\"\"\"\n# pylint: disable=c0413, c0415, c0401, e0401, e1123, r0904, w0611\nfrom __future__ import print_function\nimport os\nimport sys\nimport json\nfrom typing import List, Tuple, Dict\n\n\ndef initialize():  # nopep8\n    \"\"\"initialize routine when calling dbstore functions from script\"\"\"\n    sys.path.append(\n        os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n    )\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"acme2certifier.settings\")\n    import django\n\n    # pylint: disable=E1101\n    django.setup()\n    return django.VERSION[0]\n\n\nDJANGO_VERSION = initialize()\nfrom django.conf import settings  # nopep8\nfrom django.db import transaction  # nopep8\nfrom django.db.models import QuerySet  # nopep8\nfrom acme_srv.models import (\n    Account,\n    Authorization,\n    Cahandler,\n    Certificate,\n    Challenge,\n    Cliaccount,\n    Housekeeping,\n    Nonce,\n    Order,\n    Status,\n)  # nopep8\n\nif DJANGO_VERSION < 4:\n    import acme_srv.monkey_patches  # nopep8 lgtm [py/unused-import]\n\n\nclass DBstore(object):\n    \"\"\"helper to do datebase operations\"\"\"\n\n    def __init__(self, _debug: bool = False, logger: object = None):\n        \"\"\"init\"\"\"\n        self.logger = logger\n        self.type = \"django\"\n\n    def _account_getinstance(self, aname: str) -> QuerySet:\n        \"\"\"get account instance\"\"\"\n        self.logger.debug(\"DBStore._account_getinstance(%s)\", aname)\n        return Account.objects.get(name=aname)\n\n    def _authorization_getinstance(self, name: str) -> QuerySet:\n        \"\"\"get authorization instance\"\"\"\n        self.logger.debug(\"DBStore._authorization_getinstance(%s)\", name)\n        return Authorization.objects.get(name=name)\n\n    def _modify_key(self, mkey: str, operant: str) -> str:\n        \"\"\"quick hack\"\"\"\n        self.logger.debug(\"DBStore._modify_key(%s/%s)\", mkey, operant)\n\n        if operant == \"<=\":\n            mkey = f\"{mkey}__lte\"\n\n        self.logger.debug(\"DBStore._modify_key() ended with: %s\", mkey)\n        return mkey\n\n    def _order_getinstance(self, value: str = id, mkey: id = \"id\") -> QuerySet:\n        \"\"\"get order instance\"\"\"\n        self.logger.debug(\"DBStore._order_getinstance(%s:%s)\", mkey, value)\n        return Order.objects.get(**{mkey: value})\n\n    def _status_getinstance(self, value: str, mkey: str = \"id\") -> QuerySet:\n        \"\"\"get account instance\"\"\"\n        self.logger.debug(\"DBStore._status_getinstance(%s:%s)\", mkey, value)\n        return Status.objects.get(**{mkey: value})\n\n    def account_add(self, data_dic: Dict[str, str]) -> Tuple[str, bool]:\n        \"\"\"add account in database\"\"\"\n        self.logger.debug(\"DBStore.account_add(%s)\", data_dic)\n        account_list = self.account_lookup(\"jwk\", data_dic[\"jwk\"])\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"name\")\n\n        if \"eab_kid\" in data_dic and not data_dic[\"eab_kid\"]:\n            del data_dic[\"eab_kid\"]\n\n        if account_list:\n            created = False\n            aname = account_list[\"name\"]\n        else:\n            obj, created = Account.objects.update_or_create(\n                name=data_dic[\"name\"], defaults=data_dic\n            )\n            obj.save()\n            aname = data_dic[\"name\"]\n        return aname, created\n\n    def account_lookup(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List = [\n            \"id\",\n            \"jwk\",\n            \"name\",\n            \"contact\",\n            \"alg\",\n            \"created_at\",\n            \"eab_kid\",\n            \"status_id\",\n        ],\n    ) -> Dict[str, str]:\n        \"\"\"search account for a given id\"\"\"\n        self.logger.debug(\"DBStore.account_lookup(%s:%s)\", mkey, value)\n        account_dict = Account.objects.filter(**{mkey: value}).values(*vlist)[:1]\n        if account_dict.exists():\n            result = account_dict[0]\n        else:\n            result = None\n        return result\n\n    def account_delete(self, aname):\n        \"\"\"add account in database\"\"\"\n        self.logger.debug(\"DBStore.account_delete(%s)\", aname)\n        result = Account.objects.filter(name=aname).delete()\n        return result\n\n    def account_update(\n        self,\n        data_dic: Dict[str, str],\n        active: bool = True,  # NOSONAR # pylint: disable=unused-argument\n    ) -> int:\n        \"\"\"update existing account\"\"\"\n        self.logger.debug(\"DBStore.account_update(%s)\", data_dic)\n        obj, _created = Account.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n        self.logger.debug(\"acct_id(%s)\", obj.id)\n        return obj.id\n\n    def accountlist_get(self) -> Tuple[List[str], QuerySet]:\n        \"\"\"accountlist_get\"\"\"\n        self.logger.debug(\"DBStore.accountlist_get()\")\n        vlist = [\n            \"id\",\n            \"name\",\n            \"contact\",\n            \"eab_kid\",\n            \"created_at\",\n            \"jwk\",\n            \"alg\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__id\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__authorization__id\",\n            \"order__authorization__name\",\n            \"order__authorization__type\",\n            \"order__authorization__value\",\n            \"order__authorization__expires\",\n            \"order__authorization__token\",\n            \"order__authorization__created_at\",\n            \"order__authorization__status_id\",\n            \"order__authorization__status__id\",\n            \"order__authorization__status__name\",\n            \"order__authorization__challenge__id\",\n            \"order__authorization__challenge__name\",\n            \"order__authorization__challenge__token\",\n            \"order__authorization__challenge__expires\",\n            \"order__authorization__challenge__type\",\n            \"order__authorization__challenge__keyauthorization\",\n            \"order__authorization__challenge__created_at\",\n            \"order__authorization__challenge__status__id\",\n            \"order__authorization__challenge__status__name\",\n        ]\n        # for historical reason cert_raw an be NULL or ''; we have to consider both cases during selection\n        return vlist, list(Account.objects.filter(name__isnull=False).values(*vlist))\n\n    def authorization_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add authorization to database\"\"\"\n        self.logger.debug(\"DBStore.authorization_add(%s)\", data_dic)\n\n        # get some instance for DB insert\n        if \"order\" in data_dic:\n            data_dic[\"order\"] = self._order_getinstance(data_dic[\"order\"], \"id\")\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"name\")\n\n        # add authorization\n        obj, _created = Authorization.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n        self.logger.debug(\"auth_id(%s)\", obj.id)\n        return obj.id\n\n    def authorization_lookup(\n        self, mkey: str, value: str, vlist: List[str] = (\"type\", \"value\")\n    ) -> QuerySet:\n        \"\"\"search account for a given id\"\"\"\n        self.logger.debug(\"authorization_lookup(%s:%s:%s)\", mkey, value, vlist)\n        authz_list = Authorization.objects.filter(**{mkey: value}).values(*vlist)[::1]\n        return authz_list\n\n    def authorizations_expired_search(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"identifiers\",\n            \"created_at\",\n            \"status__id\",\n            \"status__name\",\n            \"account__id\",\n            \"account__name\",\n            \"acccount__contact\",\n        ),\n        operant: str = \"LIKE\",\n    ) -> QuerySet:\n        \"\"\"search order table for a certain key/value pair\"\"\"\n        self.logger.debug(\n            \"DBStore.authorizations_invalid_search(column:%s, pattern:%s)\", mkey, value\n        )\n\n        mkey = self._modify_key(mkey, operant)\n\n        self.logger.debug(\"DBStore.authorizations_invalid_search() ended\")\n        return (\n            Authorization.objects.filter(**{mkey: value})\n            .exclude(status__name=\"expired\")\n            .values(*vlist)\n        )\n\n    def authorization_update(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"update existing authorization\"\"\"\n        self.logger.debug(\"DBStore.authorization_update(%s)\", data_dic)\n        # get some instance for DB insert\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"name\")\n\n        if (\n            DJANGO_VERSION < 4\n            and settings.DATABASES[\"default\"][\"ENGINE\"] == \"django.db.backends.sqlite3\"\n        ):\n            self.logger.debug(\n                \"DBStore.authorization_update(): patching transaction to transform all atomic blocks into immediate transactions\"\n            )\n            with transaction.atomic(immediate=True):\n                # update authorization\n                obj, _created = Authorization.objects.update_or_create(\n                    name=data_dic[\"name\"], defaults=data_dic\n                )\n                obj.save()\n        else:\n            # update authorization\n            obj, _created = Authorization.objects.update_or_create(\n                name=data_dic[\"name\"], defaults=data_dic\n            )\n            obj.save()\n\n        self.logger.debug(\"auth_id(%s)\", obj.id)\n        return obj.id\n\n    def cahandler_add(self, data_dic: Dict[str, str]) -> Tuple[str, bool]:\n        \"\"\"add cahandler to database\"\"\"\n        self.logger.debug(\"DBStore.cahandler_add(%s)\", data_dic)\n        cahandler_list = self.cahandler_lookup(\"name\", data_dic[\"name\"])\n        if cahandler_list:\n            created = False\n            cname = cahandler_list[\"name\"]\n        else:\n            obj, created = Cahandler.objects.update_or_create(\n                name=data_dic[\"name\"], defaults=data_dic\n            )\n            obj.save()\n            cname = data_dic[\"name\"]\n        return (cname, created)\n\n    def cahandler_lookup(self, mkey: str, value: str) -> Dict[str, str]:\n        \"\"\"search cahandler for a given id\"\"\"\n        self.logger.debug(\"DBStore.cahandler_lookup(%s:%s)\", mkey, value)\n        cahandler_dict = Cahandler.objects.filter(**{mkey: value}).values(\n            \"name\", \"value1\", \"value2\", \"created_at\"\n        )[:1]\n        if cahandler_dict.exists():\n            result = cahandler_dict[0]\n        else:\n            result = None\n        return result\n\n    def challenge_add(self, value: str, mtype: str, data_dic: Dict[str, str]) -> int:\n        \"\"\"add challenge to database\"\"\"\n        self.logger.debug(\"DBStore.challenge_add(%s:%s)\", value, mtype)\n\n        # get order instance for DB insert\n        data_dic[\"authorization\"] = self._authorization_getinstance(\n            data_dic[\"authorization\"]\n        )\n\n        # replace orderstatus with an instance\n        data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"])\n\n        if (\n            DJANGO_VERSION < 4\n            and settings.DATABASES[\"default\"][\"ENGINE\"] == \"django.db.backends.sqlite3\"\n        ):\n            self.logger.debug(\n                \"DBStore.challenge_add(): patching transaction to transform all atomic blocks into immediate transactions\"\n            )\n            with transaction.atomic(immediate=True):\n                obj, _created = Challenge.objects.update_or_create(\n                    name=data_dic[\"name\"], defaults=data_dic\n                )\n                obj.save()\n        else:\n            obj, _created = Challenge.objects.update_or_create(\n                name=data_dic[\"name\"], defaults=data_dic\n            )\n            obj.save()\n\n        self.logger.debug(\"cid(%s)\", obj.id)\n        self.logger.debug(\"DBStore.challenge_add(%s:%s:%s)\", value, mtype, obj.id)\n        return obj.id\n\n    def certificate_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add csr/certificate to database\"\"\"\n        self.logger.debug(\"DBStore.certificate_add()\")\n\n        # get order instance for DB insert\n        if \"order\" in data_dic:\n            data_dic[\"order\"] = self._order_getinstance(data_dic[\"order\"], \"name\")\n        # add certificate/CSR\n        obj, _created = Certificate.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n        self.logger.debug(\"DBStore.certificate_add() ended with :%s\", obj.id)\n        return obj.id\n\n    def certificate_account_check(self, account_name: str, certificate: str) -> str:\n        \"\"\"check issuer against certificate\"\"\"\n        self.logger.debug(\"DBStore.certificate_account_check(%s)\", account_name)\n\n        result = None\n        certificate_list = self.certificate_lookup(\n            \"cert_raw\", certificate, [\"name\", \"order__name\", \"order__account__name\"]\n        )\n\n        if certificate_list:\n            if account_name:\n                # if there is an acoount name validate it against the account_name from db-query\n                if account_name == certificate_list[\"order__account__name\"]:\n                    result = certificate_list[\"order\"]\n            else:\n                # no account name given (message signed with domain key\n                result = certificate_list[\"order\"]\n\n        self.logger.debug(\"DBStore.certificate_account_check() ended with: %s\", result)\n        return result\n\n    def certificate_delete(self, mkey: str, value: str) -> QuerySet:\n        \"\"\"delete certificate from table\"\"\"\n        self.logger.debug(\"DBStore.certificate_delete(%s:%s)\", mkey, value)\n        Certificate.objects.filter(**{mkey: value}).delete()\n\n    def certificatelist_get(self) -> Tuple[List[str], List[QuerySet]]:\n        \"\"\"certificatelist_get\"\"\"\n        self.logger.debug(\"DBStore.certificatelist_get()\")\n        vlist = [\n            \"id\",\n            \"name\",\n            \"cert_raw\",\n            \"csr\",\n            \"poll_identifier\",\n            \"created_at\",\n            \"issue_uts\",\n            \"expire_uts\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__account__name\",\n            \"order__account__contact\",\n            \"order__account__created_at\",\n            \"order__account__jwk\",\n            \"order__account__alg\",\n            \"order__account__eab_kid\",\n        ]\n        # for historical reason cert_raw an be NULL or ''; we have to consider both cases during selection\n        return (\n            vlist,\n            list(\n                Certificate.objects.filter(cert_raw__isnull=False)\n                .exclude(cert_raw=\"\")\n                .values(*vlist)\n            ),\n        )\n\n    def certificate_lookup(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\"name\", \"csr\", \"cert\", \"order__name\"),\n    ) -> Dict[str, str]:\n        \"\"\"search certificate based on \"something\" \"\"\"\n        self.logger.debug(\"DBStore.certificate_lookup(%s:%s)\", mkey, value)\n        certificate_list = Certificate.objects.filter(**{mkey: value}).values(*vlist)[\n            :1\n        ]\n        if certificate_list:\n            result = certificate_list[0]\n            if \"order__name\" in result:\n                result[\"order\"] = result[\"order__name\"]\n                del result[\"order__name\"]\n        else:\n            result = None\n        self.logger.debug(\"DBStore.certificate_lookup() ended with: %s\", result)\n        return result\n\n    def certificates_search(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\"name\", \"csr\", \"cert\", \"order__name\"),\n        operant=None,\n    ) -> QuerySet:\n        \"\"\"search certificate based on \"something\" \"\"\"\n        self.logger.debug(\"DBStore.certificates_search(%s:%s)\", mkey, value)\n        mkey = self._modify_key(mkey, operant)\n        return Certificate.objects.filter(**{mkey: value}).values(*vlist)\n\n    def challenge_lookup(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\"type\", \"token\", \"status__name\"),\n    ) -> Dict[str, str]:\n        \"\"\"search account for a given id\"\"\"\n        self.logger.debug(\"DBStore.challenge_lookup(%s:%s)\", mkey, value)\n        challenge_list = Challenge.objects.filter(**{mkey: value}).values(*vlist)[:1]\n        if challenge_list:\n            result = challenge_list[0]\n            if \"status__name\" in result:\n                result[\"status\"] = result[\"status__name\"]\n                del result[\"status__name\"]\n            if \"authorization__name\" in result:\n                result[\"authorization\"] = result[\"authorization__name\"]\n                del result[\"authorization__name\"]\n        else:\n            result = None\n        return result\n\n    def challenges_search(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\"name\", \"type\", \"cert\", \"status__name\", \"token\"),\n    ) -> QuerySet:\n        \"\"\"search challenges based on \"something\" \"\"\"\n        self.logger.debug(\"DBStore.challenges_search(%s:%s)\", mkey, value)\n        return Challenge.objects.filter(**{mkey: value}).values(*vlist)\n\n    def challenge_update(self, data_dic: Dict[str, str]):\n        \"\"\"update challenge\"\"\"\n        self.logger.debug(\"challenge_update(%s)\", data_dic)\n        # replace orderstatus with an instance\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"name\")\n        obj, _created = Challenge.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n\n    def cli_jwk_load(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad account informatino and build jwk key dictionary from cliaccounts teable\"\"\"\n        self.logger.debug(\"DBStore.cli_jwk_load(%s)\", aname)\n        account_dict = Cliaccount.objects.filter(name=aname).values(\"jwk\")[:1]\n        jwk_dict = {}\n        if account_dict:\n            try:\n                jwk_dict = json.loads(account_dict[0][\"jwk\"].decode())\n            except Exception as _err:\n                self.logger.error(\n                    \"Failed to decode JWK from cliaccounts table: %s\", _err\n                )\n                jwk_dict = json.loads(account_dict[0][\"jwk\"])\n        return jwk_dict\n\n    def cli_permissions_get(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad account informations and build jwk key dictionary from cliaccounts teable\"\"\"\n        self.logger.debug(\"DBStore.cli_jwk_load(%s)\", aname)\n        account_dict = Cliaccount.objects.filter(name=aname).values(\n            \"reportadmin\", \"cliadmin\", \"certificateadmin\"\n        )[:1]\n        permissions_dict = {}\n        if account_dict.exists():\n            permissions_dict = account_dict[0]\n        return permissions_dict\n\n    def dbversion_get(self) -> Tuple[Dict[str, str], str]:\n        \"\"\"get db version from housekeeping table\"\"\"\n        self.logger.debug(\"DBStore.dbversion_get()\")\n        version_list = Housekeeping.objects.filter(name=\"dbversion\").values_list(\n            \"value\", flat=True\n        )\n        if version_list:\n            result = version_list[0]\n        else:\n            result = None\n        self.logger.debug(\"DBStore.dbversion_get() ended with %s\", result)\n        return (result, \"tools/django_update.py\")\n\n    def hkparameter_add(self, data_dic: Dict[str, str]):\n        \"\"\"add housekeeping paramter to database\"\"\"\n        self.logger.debug(\"DBStore.hkparameter_add(%s)\", data_dic)\n        obj, _created = Housekeeping.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n\n    def hkparameter_get(self, parameter: str) -> str:\n        \"\"\"get parameter from housekeeping table\"\"\"\n        self.logger.debug(\"DBStore.hkparameter_get()\")\n        result_list = Housekeeping.objects.filter(name=parameter).values_list(\n            \"value\", flat=True\n        )\n        if result_list:\n            result = result_list[0]\n        else:\n            result = None\n        self.logger.debug(\"DBStore.hkparameter_get() ended with %s\", result)\n        return result\n\n    def jwk_load(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad account informatino and build jwk key dictionary\"\"\"\n        self.logger.debug(\"DBStore.jwk_load(%s)\", aname)\n        account_dict = Account.objects.filter(name=aname, status_id=5).values(\n            \"jwk\", \"alg\"\n        )[:1]\n        jwk_dict = {}\n        if account_dict:\n            try:\n                jwk_dict = json.loads(account_dict[0][\"jwk\"].decode())\n            except Exception:\n                jwk_dict = json.loads(account_dict[0][\"jwk\"])\n            jwk_dict[\"alg\"] = account_dict[0][\"alg\"]\n        return jwk_dict\n\n    def nonce_add(self, nonce: str) -> int:\n        \"\"\"check if nonce is in datbase\n        in: nonce\n        return: rowid\"\"\"\n        self.logger.debug(\"DBStore.nonce_add(%s)\", nonce)\n        obj = Nonce(nonce=nonce)\n        obj.save()\n        return obj.id\n\n    def nonce_check(self, nonce: str) -> bool:\n        \"\"\"ceck if nonce is in datbase\n        in: nonce\n        return: true in case nonce exit, otherwise false\"\"\"\n        self.logger.debug(\"DBStore.nonce_check(%s)\", nonce)\n        nonce_list = Nonce.objects.filter(nonce=nonce).values(\"nonce\")[:1]\n        return bool(nonce_list)\n\n    def nonce_delete(self, nonce: str):\n        \"\"\"delete nonce from datbase\n        in: nonce\"\"\"\n        self.logger.debug(\"DBStore.nonce_delete(%s)\", nonce)\n        Nonce.objects.filter(nonce=nonce).delete()\n\n    def order_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add order to database\"\"\"\n        self.logger.debug(\"DBStore.order_add(%s)\", data_dic)\n        # replace accountid with instance\n        data_dic[\"account\"] = self._account_getinstance(data_dic[\"account\"])\n\n        # replace orderstatus with an instance\n        data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"id\")\n        obj, _created = Order.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n        self.logger.debug(\"order_id(%s)\", obj.id)\n        return obj.id\n\n    def order_lookup(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\n            \"name\",\n            \"notbefore\",\n            \"notafter\",\n            \"identifiers\",\n            \"status__name\",\n            \"account__name\",\n            \"expires\",\n        ),\n    ) -> Dict[str, str]:\n        \"\"\"search orders for a given ordername\"\"\"\n        self.logger.debug(\"order_lookup(%s:%s)\", mkey, value)\n        order_list = Order.objects.filter(**{mkey: value}).values(*vlist)[:1]\n        if order_list:\n            result = order_list[0]\n            if \"status__name\" in result:\n                result[\"status\"] = result[\"status__name\"]\n                del result[\"status__name\"]\n            if \"account_name\" in result:\n                result[\"account\"] = result[\"account__name\"]\n                del result[\"account__name\"]\n        else:\n            result = None\n        return result\n\n    def order_update(self, data_dic: Dict[str, str]):\n        \"\"\"update order\"\"\"\n        self.logger.debug(\"order_update(%s)\", data_dic)\n        # replace orderstatus with an instance\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = self._status_getinstance(data_dic[\"status\"], \"name\")\n        obj, _created = Order.objects.update_or_create(\n            name=data_dic[\"name\"], defaults=data_dic\n        )\n        obj.save()\n\n    def orders_invalid_search(\n        self,\n        mkey: str,\n        value: str,\n        vlist: List[str] = (\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"identifiers\",\n            \"created_at\",\n            \"status__id\",\n            \"status__name\",\n            \"account__id\",\n            \"account__name\",\n            \"acccount__contact\",\n        ),\n        operant=\"LIKE\",\n    ) -> QuerySet:\n        \"\"\"search order table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore.orders_search(column:%s, pattern:%s)\", mkey, value)\n        mkey = self._modify_key(mkey, operant)\n        return Order.objects.filter(**{mkey: value}, status__id__gt=1).values(*vlist)\n"
  },
  {
    "path": "examples/db_handler/wsgi_handler.py",
    "content": "# pylint: disable=c0302, r0904, w0102\n# -*- coding: utf-8 -*-\n\"\"\"wsgi handler for acme2certifier\"\"\"\nfrom __future__ import print_function\nimport sqlite3\nimport json\nfrom typing import List, Tuple, Dict\nimport os\n\n# pylint: disable=E0401\nfrom acme_srv.helper import datestr_to_date, load_config\nfrom acme_srv.version import __dbversion__\n\n\n# Define constants\nCOLUMN_NOT_IN_TABLE_MSG = \"Column: %s not found in %s table\"\n\n\ndef initialize():\n    \"\"\"run db_handler specific initialization functions\"\"\"\n    # pylint: disable=W0107\n    pass\n\n\ndef dict_from_row(row):\n    \"\"\"small helper to convert the output of a \"select\" command into a dictionary\"\"\"\n    return dict(zip(row.keys(), row))\n\n\nclass DBstore(object):\n    \"\"\"helper to do datebase operations\"\"\"\n\n    def __init__(self, debug: bool = False, logger: object = None, db_name: str = None):\n        \"\"\"init\"\"\"\n        self._column_cache = {}\n        self.db_name = db_name\n        self.debug = debug\n        self.dbs = None\n        self.cursor = None\n        self.logger = logger\n        self.type = \"wsgi\"\n\n        if not self.db_name:\n            cfg = load_config()\n            if \"DBhandler\" in cfg and \"dbfile\" in cfg[\"DBhandler\"]:\n                db_name = cfg[\"DBhandler\"][\"dbfile\"]\n            else:\n                db_name = os.path.dirname(__file__) + \"/\" + \"acme_srv.db\"\n\n        self.db_name = db_name\n\n        if not os.path.exists(self.db_name):\n            self._db_create()\n\n    def _columnnames_get(self, table: str) -> List[str]:\n        \"\"\"get columns of a table, with caching\"\"\"\n        self.logger.debug(\"DBStore.columns_get(%s)\", table)\n\n        if table in self._column_cache:\n            self.logger.debug(\"DBStore.columns_get(): cache hit for table %s\", table)\n            return self._column_cache[table]\n\n        self._db_open()\n        pre_statement = f\"SELECT * from {table}\"\n        self.cursor.execute(pre_statement)\n        result = [column[0] for column in self.cursor.description]\n        self._db_close()\n        self._column_cache[table] = result  # Cache the result\n\n        self.logger.debug(\"DBStore.columns_get() ended with: %s elements\", len(result))\n        return result\n\n    def _table_check(self, table: str) -> bool:\n        \"\"\"get all tables in db\"\"\"\n        self.logger.debug(\"DBStore.tables_get()\")\n        self._db_open()\n        pre_statement = \"SELECT name FROM sqlite_master WHERE type='table'\"\n        self.cursor.execute(pre_statement)\n        tables_list = [row[0] for row in self.cursor.fetchall()]\n        self._db_close()\n        result = True if table in tables_list else False\n        self.logger.debug(\"DBStore._table_check() ended with: %s\", result)\n        return result\n\n    def _identifier_check(self, table: str, identifier: str) -> bool:\n        \"\"\"check if identifier is in table\"\"\"\n        self.logger.debug(\"DBStore._identifier_check(%s, %s)\", identifier, table)\n        if \".\" in identifier:\n            # we have a table.column name\n            table, identifier = identifier.split(\".\", 1)\n            self.logger.debug(\n                \"DBStore._identifier_check(): modified table/identifier to %s/%s\",\n                table,\n                identifier,\n            )\n        elif \"__\" in identifier:\n            # we have a table__column name\n            table, identifier = identifier.split(\"__\", 1)\n            self.logger.debug(\n                \"DBStore._identifier_check(): modified table/identifier to %s/%s\",\n                table,\n                identifier,\n            )\n        if table == \"order\":\n            table = \"orders\"\n            self.logger.debug(\n                \"DBStore._identifier_check(): modified table to %s\", table\n            )\n        if self._table_check(table):\n            columnname_list = self._columnnames_get(table)\n            result = True if identifier in columnname_list else False\n        else:\n            self.logger.warning(\"Table '%s' does not exist in the database.\", table)\n            result = False\n\n        self.logger.debug(\"DBStore._identifier_check() ended with: %s\", result)\n        return result\n\n    def _account_search(\n        self, column: str, string: str, active: bool = True\n    ) -> List[str]:\n        \"\"\"search account table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._account_search(%s, %s)\", column, string)\n        if not self._identifier_check(\"account\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"account\")\n            return []\n        self._db_open()\n        try:\n            if active:\n                pre_statement = (\n                    f\"SELECT * from account WHERE {column} LIKE ? AND status_id = 5\"\n                )\n            else:\n                pre_statement = f\"SELECT * from account WHERE {column} LIKE ?\"\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchone()\n        except Exception as err:\n            self.logger.error(\n                \"Account search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = []\n        self._db_close()\n        self.logger.debug(\"DBStore._account_search() ended with: %s\", bool(result))\n        return result\n\n    def _authorization_search(self, column: str, string: str) -> List[str]:\n        \"\"\"search account table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._authorization_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"authorization\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"authorization\")\n            return []\n        if column == \"name\":\n            self.logger.debug(\"rename name to authorization.name\")\n            column = \"authorization.name\"\n        self._db_open()\n        pre_statement = f\"\"\"SELECT\n                            authorization.*,\n                            orders.id as orders__id,\n                            orders.name as order__name,\n                            status.id as status_id,\n                            status.name as status__name,\n                            account.name as order__account__name,\n                            account.eab_kid as order__account__eab_kid\n                        from authorization\n                        LEFT JOIN orders on orders.id = authorization.order_id\n                        LEFT JOIN status on status.id = authorization.status_id\n                        LEFT JOIN account on account.id = orders.account_id\n                        WHERE {column} LIKE ?\"\"\"\n        try:\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchall()\n        except Exception as err:\n            self.logger.error(\n                \"Authorization search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = []\n        self._db_close()\n        self.logger.debug(\"DBStore._authorization_search() ended\")\n        return result\n\n    def _cahandler_search(self, column: str, string: str) -> List[str]:\n        \"\"\"search cahandler table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._cahandler_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"cahandler\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"cahandler\")\n            return []\n        self._db_open()\n        pre_statement = f\"\"\"SELECT cahandler.* from cahandler WHERE {column} LIKE ?\"\"\"\n        try:\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchone()\n        except Exception as err:\n            self.logger.error(\n                \"CA handler search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = None\n        self._db_close()\n        self.logger.debug(\"DBStore._cahandler_search() ended\")\n        return result\n\n    def _certificate_account_check(\n        self,\n        account_name: str,\n        certificate_dic: Dict[str, str],\n        order_dic: Dict[str, str],\n    ) -> List[str]:\n        self.logger.debug(\"DBStore._certificate_account_check(%s)\", account_name)\n        result = None\n        if account_name:\n            # if there is an acoount name validate it against the account_name from db-query\n            if order_dic[\"account__name\"] == account_name:\n                result = certificate_dic[\"order__name\"]\n                self.logger.debug(\"message signed with account key\")\n            else:\n                self.logger.debug(\"account_name and and account_name from oder differ.\")\n        else:\n            # no account name given (message signed with domain key)\n            result = certificate_dic[\"order__name\"]\n            self.logger.debug(\"message signed with domain key\")\n        self.logger.debug(\"DBStore._certificate_account_check() ended with: %s\", result)\n        return result\n\n    def _certificate_insert(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"insert certificate\"\"\"\n        self.logger.debug(\"_certificate_insert() for %s\", data_dic[\"name\"])\n        # change order name to id but tackle cases where we cannot do this\n        try:\n            data_dic[\"order\"] = dict_from_row(\n                self._order_search(\"name\", data_dic[\"order\"])\n            )[\"id\"]\n        except Exception:\n            data_dic[\"order\"] = 0\n\n        self._db_open()\n        if \"csr\" not in data_dic:\n            data_dic[\"csr\"] = \"\"\n        if \"header_info\" not in data_dic:\n            data_dic[\"header_info\"] = \"\"\n        if \"error\" in data_dic:\n            self.cursor.execute(\n                \"\"\"INSERT INTO Certificate(name, error, order_id, csr, header_info) VALUES(:name, :error, :order, :csr, :header_info)\"\"\",\n                data_dic,\n            )\n        else:\n            self.cursor.execute(\n                \"\"\"INSERT INTO Certificate(name, csr, order_id, header_info) VALUES(:name, :csr, :order, :header_info)\"\"\",\n                data_dic,\n            )\n        self._db_close()\n        self.logger.debug(\"insert new entry for %s\", data_dic[\"name\"])\n\n        rid = self.cursor.lastrowid\n        self.logger.debug(\"_certificate_insert() ended with: %s\", rid)\n        return rid\n\n    def _certificate_update(\n        self, data_dic: Dict[str, str], exists: Dict[str, str]\n    ) -> int:\n        self.logger.debug(\n            \"_certificate_update() for %s id:%s\",\n            data_dic[\"name\"],\n            dict_from_row(exists)[\"id\"],\n        )\n        self._db_open()\n        if \"error\" in data_dic:\n            self.cursor.execute(\n                \"\"\"UPDATE Certificate SET error = :error, poll_identifier = :poll_identifier WHERE name = :name\"\"\",\n                data_dic,\n            )\n        else:\n            if \"expire_uts\" not in data_dic:\n                data_dic[\"expire_uts\"] = 0\n            if \"issue_uts\" not in data_dic:\n                data_dic[\"issue_uts\"] = 0\n            if \"replaced\" not in data_dic:\n                data_dic[\"replaced\"] = exists[\"replaced\"]\n\n            self.cursor.execute(\n                \"\"\"UPDATE Certificate SET cert = :cert, cert_raw = :cert_raw, issue_uts = :issue_uts, expire_uts = :expire_uts, renewal_info = :renewal_info, poll_identifier = :poll_identifier, replaced = :replaced, header_info = :header_info, serial = :serial, aki = :aki WHERE name = :name\"\"\",\n                data_dic,\n            )\n        self._db_close()\n        rid = dict_from_row(exists)[\"id\"]\n        self.logger.debug(\"_certificate_update() ended with: %s\", rid)\n        return rid\n\n    def _certificate_search(self, column: str, string: str) -> Dict[str, str]:\n        \"\"\"search certificate table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._certificate_search(%s, %s)\", column, string)\n        if not self._identifier_check(\"certificate\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"certificate\")\n            return {}\n        self._db_open()\n        if column != \"order__name\":\n            column = f\"certificate.{column}\"\n            self.logger.debug(f\"modified column to {column}\")\n        pre_statement = f\"\"\"SELECT certificate.*,\n                            orders.id as order__id,\n                            orders.name as order__name,\n                            orders.status_id as order__status_id,\n                            orders.profile as order__profile,\n                            account.name as order__account__name,\n                            account.eab_kid as order__account__eab_kid,\n                            account.contact as order__account__contact\n                            from certificate\n                            INNER JOIN orders on orders.id = certificate.order_id\n                            INNER JOIN account on account.id = orders.account_id\n                            WHERE {column} LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [string])\n        result = self.cursor.fetchone()\n        self._db_close()\n        self.logger.debug(\"DBStore._certificate_search() ended with: %s\", bool(result))\n        return result\n\n    def _challenge_search(self, column: str, string: str) -> List[str]:\n        \"\"\"search challenge table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._challenge_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"challenge\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"challenge\")\n            return []\n        self._db_open()\n        pre_statement = f\"\"\"\n            SELECT\n                challenge.*,\n                status.id as status__id,\n                status.name as status__name,\n                authorization.id as authorization__id,\n                authorization.name as authorization__name,\n                authorization.type as authorization__type,\n                authorization.value as authorization__value,\n                authorization.token as authorization__token,\n                orders.name as authorization__order__name,\n                account.name as authorization__order__account__name,\n                account.eab_kid as authorization__order__account__eab_kid\n            from challenge\n            INNER JOIN status on status.id = challenge.status_id\n            INNER JOIN authorization on authorization.id = challenge.authorization_id\n            INNER JOIN orders on orders.id = authorization.order_id\n            INNER JOIN account on account.id = orders.account_id\n            WHERE challenge.{column} LIKE ?\"\"\"\n        try:\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchone()\n        except Exception as err:\n            self.logger.error(\n                \"Challenge search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = []\n        self._db_close()\n        self.logger.debug(\"DBStore._challenge_search() ended\")\n        return result\n\n    def _cliaccount_search(self, column: str, string: str) -> Dict[str, str]:\n        \"\"\"search account table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._cliaccount_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"cliaccount\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"cliaccount\")\n            return {}\n        self._db_open()\n        try:\n            pre_statement = f\"SELECT * from cliaccount WHERE {column} LIKE ?\"\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchone()\n        except Exception as err:\n            self.logger.error(\n                \"CLI account search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = None\n        self._db_close()\n        self.logger.debug(\"DBStore._account_search() ended with: %s\", bool(result))\n        return result\n\n    def _db_close(self):\n        \"\"\"commit and close\"\"\"\n        # self.logger.debug('DBStore._db_close()')\n        self.dbs.commit()\n        self.dbs.close()\n        # self.logger.debug('DBStore._db_close() ended')\n\n    def _db_create(self):\n        \"\"\"create the database if dos not exist\"\"\"\n        self.logger.debug(\"DBStore._db_create(%s)\", self.db_name)\n        self._db_open()\n        # create status table\n        self.logger.debug(\"create status\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"status\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) UNIQUE NOT NULL)\n        \"\"\"\n        )\n        insert_status_statement = \"\"\"INSERT INTO status(name) VALUES(:name)\"\"\"\n        self.cursor.execute(insert_status_statement, {\"name\": \"invalid\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"pending\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"ready\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"processing\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"valid\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"expired\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"deactivated\"})\n        self.cursor.execute(insert_status_statement, {\"name\": \"revoked\"})\n        # create nonce table\n        self.logger.debug(\"create nonce\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"nonce\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"nonce\" varchar(30) NOT NULL, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create account\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"account\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"alg\" varchar(10) NOT NULL, \"jwk\" TEXT UNIQUE NOT NULL, \"contact\" TEXT NOT NULL, \"eab_kid\" varchar(255) DEFAULT \\'\\', \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\") DEFAULT 5, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create cliaccount\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"cliaccount\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"jwk\" TEXT UNIQUE NOT NULL, \"contact\" TEXT NOT NULL, \"cliadmin\" INT, \"reportadmin\" INT, \"certificateadmin\" INT, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create orders\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"orders\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) UNIQUE NOT NULL, \"notbefore\" integer DEFAULT 0, \"notafter\" integer DEFAULT 0, \"identifiers\" text NOT NULL, \"account_id\" integer NOT NULL REFERENCES \"account\" (\"id\"), \"profile\" varchar(64), \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\") DEFAULT 2, \"expires\" integer NOT NULL, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create authorization\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"authorization\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"order_id\" integer NOT NULL REFERENCES \"order\" (\"id\"), \"type\" varchar(5) NOT NULL, \"value\" text NOT NULL, \"expires\" integer, \"token\" varchar(64), \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\") DEFAULT 2, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create challenge\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"challenge\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"token\" varchar(64), \"authorization_id\" integer NOT NULL REFERENCES \"authorization\" (\"id\"), \"expires\" integer, \"type\" varchar(15) NOT NULL, \"keyauthorization\" varchar(128), \"source\" varchar(128), \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\"), \"validated\" integer DEFAULT 0, \"validation_error\" text, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.logger.debug(\"create certificate\")\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"certificate\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"cert\" text, \"cert_raw\" text, \"error\" text, \"order_id\" integer NOT NULL REFERENCES \"order\" (\"id\"), \"csr\" text NOT NULL, \"poll_identifier\" text,  \"header_info\" text, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, \"renewal_info\" text, \"aki\" text, \"serial\" text, \"issue_uts\" integer DEFAULT 0, \"expire_uts\" integer DEFAULT 0, \"replaced\" bolean DEFAULT 0)\n        \"\"\"\n        )\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"housekeeping\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(30) NOT NULL UNIQUE, \"value\" text, \"modified_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self.cursor.execute(\n            \"\"\"\n            CREATE TRIGGER [UpdateLastTime]\n                AFTER\n                UPDATE\n                ON housekeeping\n                FOR EACH ROW\n                WHEN NEW.modified_at <= OLD.modified_at\n            BEGIN\n                update housekeeping set modified_at=CURRENT_TIMESTAMP where id=OLD.id;\n            END\n        \"\"\"\n        )\n\n        self.cursor.execute(\n            f\"\"\"INSERT OR IGNORE INTO housekeeping (name, value) VALUES (\"dbversion\", \"{__dbversion__}\")\"\"\"\n        )\n        self.cursor.execute(\n            \"\"\"\n            CREATE TABLE \"cahandler\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"value1\" text, \"value2\" text, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n        \"\"\"\n        )\n        self._db_close()\n        self.logger.debug(\"DBStore._db_create() ended\")\n\n    def _db_open(self):\n        \"\"\"opens db and sets cursor\"\"\"\n        self.dbs = sqlite3.connect(self.db_name)\n        self.dbs.row_factory = sqlite3.Row\n        self.cursor = self.dbs.cursor()\n\n    def _db_update_account(self):\n        \"\"\"update account table\"\"\"\n        self.logger.debug(\"DBStore._db_update_account()\")\n\n        # add eab_kid\n        self.cursor.execute(\"\"\"PRAGMA table_info(account)\"\"\")\n        account_column_list = []\n        for column in self.cursor.fetchall():\n            account_column_list.append(column[1])\n        if \"eab_kid\" not in account_column_list:\n            self.logger.info(\"alter account table - add eab_kid\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE account ADD COLUMN eab_kid varchar(255) DEFAULT \\'\\'\"\"\"\n            )\n        if \"status_id\" not in account_column_list:\n            self.logger.info(\"alter account table - add status_id\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE account ADD COLUMN status_id integer NOT NULL REFERENCES status(id) DEFAULT 5\"\"\"\n            )\n\n    def _db_update_authorization(self):\n        \"\"\"alter orders table\"\"\"\n        self.logger.debug(\"DBStore._db_update_authorization()\")\n\n        # change identifier field to text to remove length restriction\n        self.cursor.execute(\"\"\"PRAGMA table_info(authorization)\"\"\")\n        for column in self.cursor.fetchall():\n            if column[1] == \"value\" and \"varchar\" in column[2].lower():\n                self.logger.info(\n                    \"alter authorization table - change value field type to TEXT\"\n                )\n                self.cursor.execute(\"\"\"ALTER TABLE authorization RENAME TO tmp\"\"\")\n                self.cursor.execute(\n                    \"\"\"\n                    CREATE TABLE \"authorization\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"order_id\" integer NOT NULL REFERENCES \"order\" (\"id\"), \"type\" varchar(5) NOT NULL, \"value\" text NOT NULL, \"expires\" integer, \"token\" varchar(64), \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\") DEFAULT 2, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n                \"\"\"\n                )\n                self.cursor.execute(\n                    \"\"\"INSERT INTO authorization(id, name, order_id, type, value, expires, token, status_id, created_at) SELECT id, name, order_id, type, value, expires, token, status_id, created_at  FROM tmp\"\"\"\n                )\n                self.cursor.execute(\"\"\"DROP TABLE tmp\"\"\")\n\n    def _db_update_cahandler(self):\n        \"\"\"alter cahandler table\"\"\"\n        self.logger.debug(\"DBStore._db_update_cahandler()\")\n\n        self.cursor.execute(\n            \"SELECT count(*) from sqlite_master where type='table' and name='cahandler'\"\n        )\n        if self.cursor.fetchone()[0] != 1:\n            self.logger.info(\"create cahandler table\")\n            self.cursor.execute(\n                \"\"\"\n                CREATE TABLE \"cahandler\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"value1\" text, \"value2\" text, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n            \"\"\"\n            )\n\n    def _db_update_certificate(self):\n        \"\"\"alter certificate table\"\"\"\n        self.logger.debug(\"DBStore._db_update_certificate()\")\n\n        # add poll_identifier if not existing\n        self.cursor.execute(\"\"\"PRAGMA table_info(certificate)\"\"\")\n        certificate_column_list = []\n        for column in self.cursor.fetchall():\n            certificate_column_list.append(column[1])\n        if \"poll_identifier\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add poll_identifier\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN poll_identifier text\"\"\"\n            )\n        if \"issue_uts\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add issue_uts\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN issue_uts integer DEFAULT 0\"\"\"\n            )\n        if \"expire_uts\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add expire_uts\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN expire_uts integer DEFAULT 0\"\"\"\n            )\n        if \"renewal_info\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add renewal_info\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN renewal_info text\"\"\"\n            )\n        if \"replaced\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add replaced\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN replaced boolean DEFAULT 0\"\"\"\n            )\n        if \"header_info\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add header_info\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE certificate ADD COLUMN header_info text\"\"\"\n            )\n        if \"aki\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add aki\")\n            self.cursor.execute(\"\"\"ALTER TABLE certificate ADD COLUMN aki text\"\"\")\n        if \"serial\" not in certificate_column_list:\n            self.logger.info(\"alter certificate table - add serial\")\n            self.cursor.execute(\"\"\"ALTER TABLE certificate ADD COLUMN serial text\"\"\")\n\n    def _db_update_challenge(self):\n        \"\"\"alter challenge table\"\"\"\n        self.logger.debug(\"DBStore._db_update_certificate()\")\n\n        self.cursor.execute(\"\"\"PRAGMA table_info(challenge)\"\"\")\n        challenges_column_list = []\n        for column in self.cursor.fetchall():\n            challenges_column_list.append(column[1])\n\n        if \"validated\" not in challenges_column_list:\n            self.logger.info(\"alter challenge table - add validated\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE challenge ADD COLUMN validated integer DEFAULT 0\"\"\"\n            )\n\n        if \"validation_error\" not in challenges_column_list:\n            self.logger.info(\"alter challenge table - add validation_error\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE challenge ADD COLUMN validation_error text\"\"\"\n            )\n\n        if \"source\" not in challenges_column_list:\n            self.logger.info(\"alter challenge table - add source‚\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE challenge ADD COLUMN source varchar(128)\"\"\"\n            )\n\n    def _db_update_cliaccount(self):\n        \"\"\"alter cliaccount table\"\"\"\n        self.logger.debug(\"DBStore._db_update_cliaccount()\")\n\n        self.cursor.execute(\n            \"SELECT count(*) from sqlite_master where type='table' and name='cliaccount'\"\n        )\n        if self.cursor.fetchone()[0] != 1:\n            self.logger.info(\"create cliaccount table\")\n            self.cursor.execute(\n                \"\"\"\n                CREATE TABLE \"cliaccount\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) NOT NULL UNIQUE, \"jwk\" TEXT UNIQUE NOT NULL, \"contact\" TEXT NOT NULL, \"cliadmin\" INT, \"reportadmin\" INT, \"certificateadmin\" INT, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n            \"\"\"\n            )\n\n    def _db_update_housekeeping(self):\n        \"\"\"alter housekeeping table\"\"\"\n        self.logger.debug(\"DBStore._db_update_housekeeping()\")\n\n        # housekeeping table\n        self.cursor.execute(\n            \"SELECT count(*) from sqlite_master where type='table' and name='housekeeping'\"\n        )\n        if self.cursor.fetchone()[0] != 1:\n            self.logger.info(\"create housekeeping table and trigger\")\n            self.cursor.execute(\n                \"\"\"\n                CREATE TABLE \"housekeeping\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(30) NOT NULL UNIQUE, \"value\" text, \"modified_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n            \"\"\"\n            )\n            self.cursor.execute(\n                \"\"\"\n                CREATE TRIGGER [UpdateLastTime]\n                    AFTER\n                    UPDATE\n                    ON housekeeping\n                    FOR EACH ROW\n                    WHEN NEW.modified_at <= OLD.modified_at\n                BEGIN\n                    update housekeeping set modified_at=CURRENT_TIMESTAMP where id=OLD.id;\n                END\n            \"\"\"\n            )\n        else:\n            self.cursor.execute(\"\"\"PRAGMA table_info(housekeeping)\"\"\")\n            for column in self.cursor.fetchall():\n                if (\n                    column[1] == \"name\" and column[2].lower() == \"varchar(15)\"\n                ):  # pragma: no cover\n                    self.logger.info(\n                        \"alter housekeeping table  - change size of the name field to 30\"\n                    )  # pragma: no cover\n                    self.cursor.execute(\n                        \"\"\"ALTER TABLE housekeeping RENAME TO tmp_hk\"\"\"\n                    )  # pragma: no cover\n                    self.cursor.execute(\n                        \"\"\"\n                        CREATE TABLE \"housekeeping\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(30) NOT NULL UNIQUE, \"value\" text, \"modified_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n                    \"\"\"\n                    )  # pragma: no cover\n                    self.cursor.execute(\n                        \"\"\"INSERT INTO housekeeping(id, name, value, modified_at) SELECT id, name, value, modified_at  FROM tmp_hk\"\"\"\n                    )  # pragma: no cover\n                    self.cursor.execute(\"\"\"DROP TABLE tmp_hk\"\"\")  # pragma: no cover\n\n    def _db_update_orders(self):\n        \"\"\"alter orders table\"\"\"\n        self.logger.debug(\"DBStore._db_update_orders()\")\n\n        order_column_list = []\n        # change identifier field to text to remove length restriction\n        self.cursor.execute(\"\"\"PRAGMA table_info(orders)\"\"\")\n        for column in self.cursor.fetchall():\n            order_column_list.append(column[1])\n            if column[1] == \"identifiers\" and \"varchar\" in column[2].lower():\n                self.logger.info(\n                    \"alter orders table - change identifier field type to TEXT\"\n                )\n                self.cursor.execute(\"\"\"ALTER TABLE orders RENAME TO tmp\"\"\")\n                self.cursor.execute(\n                    \"\"\"\n                    CREATE TABLE \"orders\" (\"id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" varchar(15) UNIQUE NOT NULL, \"notbefore\" integer DEFAULT 0, \"notafter\" integer DEFAULT 0, \"identifiers\" text NOT NULL, \"account_id\" integer NOT NULL REFERENCES \"account\" (\"id\"), \"status_id\" integer NOT NULL REFERENCES \"status\" (\"id\") DEFAULT 2, \"expires\" integer NOT NULL, \"created_at\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n                \"\"\"\n                )\n                self.cursor.execute(\n                    \"\"\"INSERT INTO orders(id, name, notbefore, notafter, identifiers, account_id, status_id, expires, created_at) SELECT id, name, notbefore, notafter, identifiers, account_id, status_id, expires, created_at  FROM tmp\"\"\"\n                )\n                self.cursor.execute(\"\"\"DROP TABLE tmp\"\"\")\n\n        if \"profile\" not in order_column_list:\n            self.logger.info(\"alter challenge orders - add profile\")\n            self.cursor.execute(\n                \"\"\"ALTER TABLE orders ADD COLUMN profile varchar(64) DEFAULT \\'\\'\"\"\"\n            )\n\n    def _db_update_status(self):\n        \"\"\"update status table\"\"\"\n        self.logger.debug(\"DBStore._db_update_status()\")\n\n        # add additional values to status table\n        pre_statement = \"SELECT * from status WHERE status.name LIKE ?\"\n        self.cursor.execute(pre_statement, [\"deactivated\"])\n        if not self.cursor.fetchone():\n            self.logger.info(\"adding additional status\")\n            insert_status_statement = \"\"\"INSERT INTO status(name) VALUES(:name)\"\"\"\n            self.cursor.execute(insert_status_statement, {\"name\": \"expired\"})\n            self.cursor.execute(insert_status_statement, {\"name\": \"deactivated\"})\n            self.cursor.execute(insert_status_statement, {\"name\": \"revoked\"})\n\n    def _order_search(self, column: str, string: str) -> List[str]:\n        \"\"\"search order table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._order_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"orders\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"orders\")\n            return []\n        self._db_open()\n        pre_statement = f\"\"\"\n                    SELECT\n                        orders.*,\n                        status.name as status__name,\n                        status.id as status__id,\n                        account.name as account__name,\n                        account.id as account_id,\n                        account.eab_kid as account__eab_kid,\n                        account.contact as account__contact\n                    from orders\n                    INNER JOIN status on status.id = orders.status_id\n                    INNER JOIN account on account.id = orders.account_id\n                    WHERE orders.{column} LIKE ?\"\"\"\n        try:\n            self.cursor.execute(pre_statement, [string])\n            result = self.cursor.fetchone()\n        except Exception as err:\n            self.logger.error(\n                \"Order search failed for column '%s' and pattern '%s': %s\",\n                column,\n                string,\n                err,\n            )\n            result = []\n        self._db_close()\n        self.logger.debug(\"DBStore._order_search() ended\")\n        return result\n\n    def _status_search(self, column: str, string: str) -> Tuple[int, str]:\n        \"\"\"search status table for a certain key/value pair\"\"\"\n        self.logger.debug(\"DBStore._status_search(%s, %s)\", column, string)\n\n        if not self._identifier_check(\"status\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"status\")\n            return (None, None)\n        self._db_open()\n        pre_statement = f\"SELECT * from status WHERE status.{column} LIKE ?\"\n        self.cursor.execute(pre_statement, [string])\n        result = self.cursor.fetchone()\n        self._db_close()\n        self.logger.debug(\"DBStore._status_search() ended\")\n        return result\n\n    def account_add(self, data_dic):\n        \"\"\"add account in database\"\"\"\n        self.logger.debug(\"DBStore.account_add(%s)\", data_dic)\n\n        # add eab_kid field if not existing\n        if \"eab_kid\" not in data_dic:\n            data_dic[\"eab_kid\"] = \"\"\n\n        # we need this for compability with django\n        created = False\n        # check if we alredy have an entry for the key\n        exists = self._account_search(\"jwk\", data_dic[\"jwk\"])\n        self._db_open()\n        if bool(exists):\n            # update\n            aname = exists[1]\n            self.logger.debug(\"account exists: %s id: %s\", aname, exists[0])\n            self.cursor.execute(\n                \"\"\"UPDATE ACCOUNT SET alg = :alg, jwk = :jwk, contact = :contact WHERE jwk = :jwk\"\"\",\n                data_dic,\n            )\n        else:\n            # insert\n            self.cursor.execute(\n                \"\"\"INSERT INTO ACCOUNT(alg, jwk, contact, name, eab_kid) VALUES(:alg, :jwk, :contact, :name, :eab_kid)\"\"\",\n                data_dic,\n            )\n            aname = data_dic[\"name\"]\n            created = True\n\n        self._db_close()\n        self.logger.debug(\"DBStore.account_add() ended\")\n        return (aname, created)\n\n    def account_delete(self, aname: str) -> bool:\n        \"\"\"add account in database\"\"\"\n        self.logger.debug(\"DBStore.account_delete(%s)\", aname)\n        self._db_open()\n        pre_statement = \"DELETE FROM account WHERE name LIKE ?\"\n        self.cursor.execute(pre_statement, [aname])\n        result = bool(self.cursor.rowcount)\n        self._db_close()\n        self.logger.debug(\"DBStore.account_delete() ended\")\n        return result\n\n    def account_lookup(\n        self,\n        column: str,\n        string: str,\n    ) -> Dict[str, str]:\n        \"\"\"lookup account table for a certain key/value pair and return id\"\"\"\n        self.logger.debug(\n            \"DBStore.account_lookup(column:%s, pattern:%s)\", column, string\n        )\n        try:\n            result = dict_from_row(self._account_search(column, string))\n        except Exception as _err:\n            result = {}\n        if \"created_at\" in result:\n            result[\"created_at\"] = datestr_to_date(\n                result[\"created_at\"], \"%Y-%m-%d %H:%M:%S\"\n            )\n        self.logger.debug(\"DBStore.account_lookup() ended\")\n        return result\n\n    def account_update(\n        self, data_dic: Dict[str, str], active: bool = True\n    ) -> List[str]:\n        \"\"\"update existing account\"\"\"\n        self.logger.debug(\"DBStore.account_update(%s)\", data_dic)\n\n        try:\n            lookup = dict_from_row(self._account_search(\"name\", data_dic[\"name\"]))\n        except Exception as _err:\n            lookup = None\n\n        if lookup:\n            if \"alg\" not in data_dic:\n                data_dic[\"alg\"] = lookup[\"alg\"]\n            if \"contact\" not in data_dic:\n                data_dic[\"contact\"] = lookup[\"contact\"]\n            if \"jwk\" not in data_dic:\n                data_dic[\"jwk\"] = lookup[\"jwk\"]\n            if \"status_id\" not in data_dic:\n                data_dic[\"status_id\"] = lookup[\"status_id\"]\n            self._db_open()\n            self.cursor.execute(\n                \"\"\"UPDATE account SET alg = :alg, contact = :contact, jwk = :jwk, status_id = :status_id WHERE name = :name\"\"\",\n                data_dic,\n            )\n            if active:\n                self.cursor.execute(\n                    \"\"\"SELECT id FROM account WHERE name=:name AND status_id = 5\"\"\",\n                    {\"name\": data_dic[\"name\"]},\n                )\n            else:\n                self.cursor.execute(\n                    \"\"\"SELECT id FROM account WHERE name=:name\"\"\",\n                    {\"name\": data_dic[\"name\"]},\n                )\n            result = self.cursor.fetchone()[0]\n            self._db_close()\n        else:\n            result = None\n        self.logger.debug(\"DBStore.account_update() ended\")\n        return result\n\n    def accountlist_get(self) -> Tuple[List[str], List[str]]:\n        \"\"\"accountlist_get\"\"\"\n        self.logger.debug(\"DBStore.accountlist_get()\")\n        vlist = [\n            \"id\",\n            \"name\",\n            \"eab_kid\",\n            \"contact\",\n            \"created_at\",\n            \"jwk\",\n            \"alg\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__id\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__authorization__id\",\n            \"order__authorization__name\",\n            \"order__authorization__type\",\n            \"order__authorization__value\",\n            \"order__authorization__expires\",\n            \"order__authorization__token\",\n            \"order__authorization__created_at\",\n            \"order__authorization__status__id\",\n            \"order__authorization__status__name\",\n            \"order__authorization__challenge__id\",\n            \"order__authorization__challenge__name\",\n            \"order__authorization__challenge__token\",\n            \"order__authorization__challenge__expires\",\n            \"order__authorization__challenge__type\",\n            \"order__authorization__challenge__keyauthorization\",\n            \"order__authorization__challenge__created_at\",\n            \"order__authorization__challenge__status__id\",\n            \"order__authorization__challenge__status__name\",\n        ]\n\n        self._db_open()\n\n        pre_statement = \"\"\"SELECT account.*,\n                           orders.id as order__id,\n                           orders.name as order__name,\n                           orders.status_id as order__status,\n                           orders.notbefore as order__notbefore,\n                           orders.notafter as order__notafter,\n                           orders.expires as order__expires,\n                           orders.identifiers as order__identifiers,\n                           orders.created_at as order__created_at,\n                           orders.status_id as order__status__id,\n                           order_status.name as order__status__name,\n                           authorization.id as order__authorization__id,\n                           authorization.name as order__authorization__name,\n                           authorization.type as order__authorization__type,\n                           authorization.value as order__authorization__value,\n                           authorization.expires as order__authorization__expires,\n                           authorization.token as order__authorization__token,\n                           authorization.created_at as order__authorization__created_at,\n                           authorization.status_id as order__authorization__status__id,\n                           auth_status.name as order__authorization__status__name,\n                           challenge.id as order__authorization__challenge__id,\n                           challenge.name as order__authorization__challenge__name,\n                           challenge.token as order__authorization__challenge__token,\n                           challenge.expires as order__authorization__challenge__expires,\n                           challenge.type as order__authorization__challenge__type,\n                           challenge.keyauthorization as order__authorization__challenge__keyauthorization,\n                           challenge.created_at as order__authorization__challenge__created_at,\n                           challenge.status_id as order__authorization__challenge__status__id,\n                           chall_status.name as order__authorization__challenge__status__name\n                           from account\n                           JOIN orders on orders.account_id = account.id\n                           JOIN authorization on authorization.order_id = orders.id\n                           JOIN challenge on challenge.authorization_id = authorization.id\n                           JOIN status as order_status on order_status.id = orders.status_id\n                           JOIN status as auth_status on auth_status.id = authorization.status_id\n                           JOIN status as chall_status on chall_status.id = challenge.status_id\"\"\"\n\n        self.cursor.execute(pre_statement)\n        rows = self.cursor.fetchall()\n\n        # process results\n        account_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n\n            account_list.append(result)\n\n        self._db_close()\n        return (vlist, account_list)\n\n    def authorization_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add authorization to database\"\"\"\n        self.logger.debug(\"DBStore.authorization_add(%s)\", data_dic)\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"INSERT INTO authorization(name, order_id, type, value) VALUES(:name, :order, :type, :value)\"\"\",\n            data_dic,\n        )\n        rid = self.cursor.lastrowid\n        self._db_close()\n        self.logger.debug(\"DBStore.authorization_add() ended with: %s\", rid)\n        return rid\n\n    def authorization_lookup(\n        self, column: str, string: str, vlist: List[str] = (\"type\", \"value\")\n    ) -> List[str]:\n        \"\"\"search account for a given id\"\"\"\n        self.logger.debug(\n            \"DBStore.authorization_lookup(column:%s, pattern:%s)\", column, string\n        )\n\n        try:\n            lookup = self._authorization_search(column, string)\n        except Exception as err:\n            self.logger.error(\n                \"Authorization lookup(column:%s, pattern:%s) failed with err: %s\",\n                column,\n                string,\n                err,\n            )\n            lookup = []\n\n        authz_list = []\n        for row in lookup:\n            row_dic = dict_from_row(row)\n            tmp_dic = {}\n            for ele in vlist:\n                tmp_dic[ele] = row_dic[ele]\n            authz_list.append(tmp_dic)\n        self.logger.debug(\"DBStore.authorization_lookup() ended\")\n        return authz_list\n\n    def authorizations_expired_search(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"value\",\n            \"created_at\",\n            \"token\",\n            \"status__id\",\n            \"status__name\",\n            \"order__id\",\n            \"order__name\",\n        ),\n        operant=\"LIKE\",\n    ) -> List[str]:\n        \"\"\"search order table for a certain key/value pair\"\"\"\n        self.logger.debug(\n            \"DBStore.authorizations_expired_search(column:%s, pattern:%s)\",\n            column,\n            string,\n        )\n        if not self._identifier_check(\"authorization\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"authorization\")\n            return []\n        self._db_open()\n        pre_statement = f\"\"\"SELECT\n                                authorization.*,\n                                status.name as status__name,\n                                status.id as status__id,\n                                orders.name as order__name,\n                                orders.id as order__id\n                                FROM authorization\n                            LEFT JOIN status on status.id = authorization.status_id\n                            LEFT JOIN orders on orders.id = authorization.order_id\n                            WHERE status__name NOT LIKE 'expired' AND authorization.{column} {operant} ?\"\"\"\n\n        self.cursor.execute(pre_statement, [string])\n        rows = self.cursor.fetchall()\n\n        authorization_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n            authorization_list.append(result)\n\n        self._db_close()\n        self.logger.debug(\"DBStore.authorizations_expired_search-() ended\")\n        return authorization_list\n\n    def authorization_update(self, data_dic: Dict[str, str]) -> List[str]:\n        \"\"\"update existing authorization\"\"\"\n        self.logger.debug(\"DBStore.authorization_update(%s)\", data_dic)\n\n        lookup = self._authorization_search(\"name\", data_dic[\"name\"])\n        if lookup:\n            lookup = dict_from_row(lookup[0])\n            if \"status\" in data_dic:\n                data_dic[\"status\"] = dict_from_row(\n                    self._status_search(\"name\", data_dic[\"status\"])\n                )[\"id\"]\n            else:\n                data_dic[\"status\"] = lookup[\"status_id\"]\n            if \"token\" not in data_dic:\n                data_dic[\"token\"] = lookup[\"token\"]\n            if \"expires\" not in data_dic:\n                data_dic[\"expires\"] = lookup[\"expires\"]\n\n            self._db_open()\n            self.cursor.execute(\n                \"\"\"UPDATE authorization SET status_id = :status, token = :token, expires = :expires WHERE name = :name\"\"\",\n                data_dic,\n            )\n            self.cursor.execute(\n                \"\"\"SELECT id FROM authorization WHERE name=:name\"\"\",\n                {\"name\": data_dic[\"name\"]},\n            )\n            result = self.cursor.fetchone()[0]\n            self._db_close()\n        else:\n            result = None\n        self.logger.debug(\"DBStore.authorization_update() ended\")\n        return result\n\n    def certificate_account_check(\n        self, account_name: str, certificate: str\n    ) -> List[str]:\n        \"\"\"check issuer against certificate\"\"\"\n        self.logger.debug(\"DBStore.certificate_account_check(%s)\", account_name)\n\n        # search certificate table to get the order-id\n        certificate_dic = self.certificate_lookup(\n            \"cert_raw\", certificate, [\"name\", \"order__name\"]\n        )\n\n        result = None\n\n        # search order table to get the account-name based on the order-id\n        if \"order__name\" in certificate_dic:\n            order_dic = self.order_lookup(\n                \"name\", certificate_dic[\"order__name\"], [\"name\", \"account__name\"]\n            )\n            if order_dic:\n                if \"account__name\" in order_dic:\n                    result = self._certificate_account_check(\n                        account_name, certificate_dic, order_dic\n                    )\n                else:\n                    self.logger.debug(\"account_name missing in order_dic\")\n            else:\n                self.logger.debug(\"order_dic empty\")\n\n        self.logger.debug(\"DBStore.certificate_account_check() ended with: %s\", result)\n        return result\n\n    def cahandler_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add cahandler values to database\"\"\"\n        self.logger.debug(\"DBStore.cahandler_add(%s)\", data_dic)\n        if \"value2\" not in data_dic:\n            data_dic[\"value2\"] = \"\"\n\n        # check if we alredy have an entry for the key\n        exists = self.cahandler_lookup(\"name\", data_dic[\"name\"], [\"id\", \"name\"])\n        self._db_open()\n        if bool(exists):\n            # update\n            self.logger.debug(f'parameter exists: name id: {data_dic[\"name\"]}')\n            self.cursor.execute(\n                \"\"\"UPDATE CAHANDLER SET name = :name, value1 = :value1, 'value2' = :value2 WHERE name = :name\"\"\",\n                data_dic,\n            )\n            rid = exists[\"id\"]\n        else:\n            # insert\n            self.cursor.execute(\n                \"\"\"INSERT INTO cahandler(name, value1, value2) VALUES(:name, :value1, :value2)\"\"\",\n                data_dic,\n            )\n            rid = self.cursor.lastrowid\n\n        self._db_close()\n        self.logger.debug(\"DBStore.authorization_add() ended with: %s\", rid)\n        return rid\n\n    def cahandler_lookup(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = [\"name\", \"value1\", \"value2\", \"created_at\"],\n    ) -> Dict[str, str]:\n        \"\"\"lookup ca handler\"\"\"\n        self.logger.debug(\n            \"DBStore.cahandler_lookup(column:%s, pattern:%s)\", column, string\n        )\n\n        try:\n            lookup = dict_from_row(self._cahandler_search(column, string))\n        except Exception:\n            lookup = None\n\n        result = {}\n        if lookup:\n            for ele in vlist:\n                result[ele] = lookup[ele]\n        else:\n            result = {}\n\n        self.logger.debug(\"DBStore.cahandler_lookup() ended\")\n        return result\n\n    def cliaccount_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add cli user\"\"\"\n        self.logger.debug(\"DBStore.cliuser_add(%s)\", data_dic[\"name\"])\n        exists = self._cliaccount_search(\"name\", data_dic[\"name\"])\n\n        rid = None\n        self._db_open()\n        if bool(exists):\n            self.logger.debug(\"cliaccount exists: name id: %s\", data_dic[\"name\"])\n            if \"contact\" not in data_dic:\n                data_dic[\"contact\"] = exists[\"contact\"]\n            if \"jwk\" not in data_dic:\n                data_dic[\"jwk\"] = exists[\"jwk\"]\n            self.cursor.execute(\n                \"\"\"UPDATE cliaccount SET name = :name, jwk = :jwk, 'contact' = :contact, 'reportadmin' = :reportadmin,  'cliadmin' = :cliadmin, 'certificateadmin' = :certificateadmin WHERE name = :name\"\"\",\n                data_dic,\n            )\n            rid = exists[\"id\"]\n        else:\n            self.cursor.execute(\n                \"\"\"INSERT INTO cliaccount(name, jwk, contact, reportadmin, cliadmin, certificateadmin) VALUES(:name, :jwk, :contact, :reportadmin, :cliadmin, :certificateadmin)\"\"\",\n                data_dic,\n            )\n            rid = self.cursor.lastrowid\n        self._db_close()\n        self.logger.debug(\"DBStore.cliaccount_add() ended with: %s\", rid)\n        return rid\n\n    def cliaccount_delete(self, data_dic: Dict[str, str]):\n        \"\"\"add cli user\"\"\"\n        self.logger.debug(\"DBStore.cliaccount_delete(%s)\", data_dic[\"name\"])\n\n        exists = self._cliaccount_search(\"name\", data_dic[\"name\"])\n        if exists:\n            self._db_open()\n            self.cursor.execute(\"\"\"DELETE FROM cliaccount WHERE name=:name\"\"\", data_dic)\n            self._db_close()\n        else:\n            self.logger.error(\n                \"CLI account delete failed: no entry found for kid '%s'\",\n                data_dic[\"name\"],\n            )\n        self.logger.debug(\"DBStore.cliaccount_delete() ended\")\n\n    def cliaccountlist_get(self) -> List[str]:\n        \"\"\"get cli accout list\"\"\"\n        self.logger.debug(\"DBStore.cliaccountlist_get()\")\n        vlist = [\n            \"id\",\n            \"name\",\n            \"jwk\",\n            \"contact\",\n            \"created_at\",\n            \"cliadmin\",\n            \"reportadmin\",\n            \"certificateadmin\",\n        ]\n\n        self._db_open()\n        pre_statement = \"\"\"SELECT cliaccount.*\n                            from cliaccount\n                            WHERE cliaccount.name IS NOT NULL\"\"\"\n\n        self.cursor.execute(pre_statement)\n        rows = self.cursor.fetchall()\n        # process results\n        account_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n            account_list.append(result)\n\n        self._db_close()\n        return account_list\n\n    def certificate_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add csr/certificate to database\"\"\"\n        self.logger.debug(\"DBStore.certificate_add(%s)\", data_dic[\"name\"])\n        # check if we alredy have an entry for the key\n        exists = self._certificate_search(\"name\", data_dic[\"name\"])\n\n        if bool(exists):\n            if \"poll_identifier\" not in data_dic:\n                data_dic[\"poll_identifier\"] = exists[\"poll_identifier\"]\n            if \"renewal_info\" not in data_dic:\n                data_dic[\"renewal_info\"] = exists[\"renewal_info\"]\n            if \"header_info\" not in data_dic:\n                data_dic[\"header_info\"] = exists[\"header_info\"]\n            if \"aki\" not in data_dic:\n                data_dic[\"aki\"] = exists[\"aki\"]\n            if \"serial\" not in data_dic:\n                data_dic[\"serial\"] = exists[\"serial\"]\n            rid = self._certificate_update(data_dic, exists)\n        else:\n            rid = self._certificate_insert(data_dic)\n\n        self.logger.debug(\"DBStore.certificate_add() ended with: %s\", rid)\n        return rid\n\n    def certificate_delete(self, mkey: str, string: str):\n        \"\"\"delete certificate from table\"\"\"\n        self.logger.debug(\"DBStore.certificate_delete(%s:%s)\", mkey, string)\n\n        if not self._identifier_check(\"certificate\", mkey):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, mkey, \"certificate\")\n        else:\n            self._db_open()\n            pre_statement = f\"\"\"DELETE from certificate WHERE {mkey} = ?\"\"\"\n            self.cursor.execute(pre_statement, [string])\n            self._db_close()\n\n    def certificatelist_get(self) -> Tuple[List[str], List[str]]:\n        \"\"\"certificatelist_get\"\"\"\n        self.logger.debug(\"DBStore.certificatelist_get()\")\n        vlist = [\n            \"id\",\n            \"name\",\n            \"cert_raw\",\n            \"csr\",\n            \"poll_identifier\",\n            \"created_at\",\n            \"issue_uts\",\n            \"expire_uts\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__account__name\",\n            \"order__account__contact\",\n            \"order__account__created_at\",\n            \"order__account__jwk\",\n            \"order__account__alg\",\n            \"order__account__eab_kid\",\n        ]\n\n        self._db_open()\n        pre_statement = \"\"\"SELECT certificate.*,\n                            orders.id as order__id,\n                            orders.name as order__name,\n                            orders.status_id as order__status__name,\n                            orders.notbefore as order__notbefore,\n                            orders.notafter as order__notafter,\n                            orders.expires as order__expires,\n                            orders.identifiers as order__identifiers,\n                            account.name as order__account__name,\n                            account.contact as order__account__contact,\n                            account.created_at as order__account__created_at,\n                            account.jwk as order__account__jwk,\n                            account.alg as order__account__alg,\n                            account.eab_kid as order__account__eab_kid\n                            from certificate\n                            INNER JOIN orders on orders.id = certificate.order_id\n                            INNER JOIN account on account.id = orders.account_id\n                            WHERE certificate.cert_raw IS NOT NULL\"\"\"\n\n        self.cursor.execute(pre_statement)\n        rows = self.cursor.fetchall()\n        # process results\n        cert_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n            cert_list.append(result)\n\n        self._db_close()\n        return (vlist, cert_list)\n\n    def certificate_lookup(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\"name\", \"csr\", \"cert\", \"order__name\"),\n    ) -> Dict[str, str]:\n        \"\"\"search certificate based on \"something\" \"\"\"\n        self.logger.debug(\"DBstore.certificate_lookup(%s:%s)\", column, string)\n\n        try:\n            lookup = dict_from_row(self._certificate_search(column, string))\n        except Exception:\n            lookup = None\n\n        result = {}\n        if lookup:\n            for ele in vlist:\n                result[ele] = lookup[ele]\n                if ele == \"order__name\":\n                    result[\"order\"] = lookup[ele]\n        else:\n            result = {}\n\n        self.logger.debug(\"DBStore.certificate_lookup() ended with: %s\", result)\n        return result\n\n    def certificates_search(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\"name\", \"csr\", \"cert\", \"order__name\"),\n        operant=\"LIKE\",\n    ) -> List[str]:\n        \"\"\"search certificate table for a certain key/value pair\"\"\"\n        self.logger.debug(\n            \"DBStore.certificates_search(column:%s, pattern:%s)\", column, string\n        )\n        if not self._identifier_check(\"certificate\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"certificate\")\n            return []\n\n        self._db_open()\n        if column == \"order__status_id\":\n            column = \"orders.status_id\"\n            self.logger.debug(\"modified column to %s\", column)\n\n        pre_statement = f\"\"\"SELECT certificate.*,\n                            orders.id as order__id,\n                            orders.name as order__name,\n                            orders.profile as order__profile,\n                            orders.status_id as order__status_id,\n                            account.name as order__account__name\n                            from certificate\n                            INNER JOIN orders on orders.id = certificate.order_id\n                            INNER JOIN account on account.id = orders.account_id\n                            WHERE {column} {operant} ?\"\"\"\n        self.cursor.execute(pre_statement, [string])\n        rows = self.cursor.fetchall()\n        cert_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n                    if ele == \"order__name\":\n                        result[\"order\"] = lookup[ele]\n            cert_list.append(result)\n\n        self._db_close()\n        self.logger.debug(\"DBStore.certificates_search() ended\")\n        return cert_list\n\n    def challenges_search(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\"name\", \"type\", \"status__name\", \"token\"),\n    ) -> List[str]:\n        \"\"\"search challenge table for a certain key/value pair\"\"\"\n        self.logger.debug(\n            \"DBStore._challenge_search(column:%s, pattern:%s)\", column, string\n        )\n        if not self._identifier_check(\"challenge\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"challenge\")\n            return []\n\n        self._db_open()\n        pre_statement = f\"\"\"\n            SELECT\n                challenge.*,\n                status.id as status__id,\n                status.name as status__name,\n                authorization.id as authorization__id,\n                authorization.name as authorization__name,\n                authorization.type as authorization__type,\n                authorization.value as authorization__value,\n                authorization.token as authorization__token,\n                orders.name as authorization__order__name,\n                account.name as authorization__order__account__name\n            from challenge\n            INNER JOIN status on status.id = challenge.status_id\n            INNER JOIN authorization on authorization.id = challenge.authorization_id\n            INNER JOIN orders on orders.id = authorization.order_id\n            INNER JOIN account on account.id = orders.account_id\n            WHERE {column} LIKE ?\"\"\"\n        self.cursor.execute(pre_statement, [string])\n        rows = self.cursor.fetchall()\n        challenge_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n                    if ele == \"status__name\":\n                        result[\"status\"] = lookup[ele]\n            challenge_list.append(result)\n        self._db_close()\n        self.logger.debug(\"DBStore._challenge_search() ended\")\n        return challenge_list\n\n    def challenge_add(self, value: str, mtype: str, data_dic: Dict[str, str]) -> int:\n        \"\"\"add challenge to database\"\"\"\n        self.logger.debug(\"DBStore.challenge_add(%s:%s)\", value, mtype)\n        authorization = self.authorization_lookup(\n            \"name\", data_dic[\"authorization\"], [\"id\"]\n        )\n\n        if \"status\" not in data_dic:\n            data_dic[\"status\"] = 2\n        if \"keyauthorization\" not in data_dic:\n            data_dic[\"keyauthorization\"] = None\n        if authorization:\n            data_dic[\"authorization\"] = authorization[0][\"id\"]\n            self._db_open()\n            self.cursor.execute(\n                \"\"\"INSERT INTO challenge(name, token, authorization_id, expires, type, status_id, keyauthorization) VALUES(:name, :token, :authorization, :expires, :type, :status, :keyauthorization)\"\"\",\n                data_dic,\n            )\n            rid = self.cursor.lastrowid\n            self._db_close()\n        else:\n            rid = None\n        self.logger.debug(\"DBStore.challenge_add() ended\")\n        return rid\n\n    def challenge_lookup(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\"type\", \"token\", \"status__name\"),\n    ) -> Dict[str, str]:\n        \"\"\"search account for a given id\"\"\"\n        self.logger.debug(\"DBStore.challenge_lookup(%s:%s)\", column, string)\n\n        try:\n            lookup = dict_from_row(self._challenge_search(column, string))\n        except Exception:\n            lookup = None\n\n        result = {}\n        if lookup:\n            for ele in vlist:\n                if ele == \"status__name\":\n                    result[\"status\"] = lookup[\"status__name\"]\n                elif ele == \"authorization__name\":\n                    result[\"authorization\"] = lookup[\"authorization__name\"]\n                else:\n                    result[ele] = lookup[ele]\n\n        self.logger.debug(\"DBStore.challenge_lookup() ended with:%s\", result)\n        return result\n\n    def challenge_update(self, data_dic: Dict[str, str]):\n        \"\"\"update challenge\"\"\"\n        self.logger.debug(\"DBStore.challenge_update(%s)\", data_dic)\n        lookup = self._challenge_search(\"name\", data_dic[\"name\"])\n        lookup = dict_from_row(lookup)\n\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = dict_from_row(\n                self._status_search(\"name\", data_dic[\"status\"])\n            )[\"id\"]\n        else:\n            data_dic[\"status\"] = lookup[\"status__id\"]\n\n        if \"keyauthorization\" not in data_dic:\n            data_dic[\"keyauthorization\"] = lookup[\"keyauthorization\"]\n\n        if \"validated\" not in data_dic:\n            data_dic[\"validated\"] = lookup[\"validated\"]\n\n        if \"source\" not in data_dic:\n            data_dic[\"source\"] = lookup[\"source\"]\n\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"UPDATE challenge SET status_id = :status, keyauthorization = :keyauthorization, source= :source, validated = :validated WHERE name = :name\"\"\",\n            data_dic,\n        )\n        self._db_close()\n        self.logger.debug(\"DBStore.challenge_update() ended\")\n\n    def cli_jwk_load(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad cliaccount information and build jwk key dictionary\"\"\"\n        self.logger.debug(\"DBStore.cli_jwk_load(%s)\", aname)\n        account_list = self._cliaccount_search(\"name\", aname)\n        jwk_dict = {}\n        if account_list:\n            jwk_dict = json.loads(account_list[2])\n        self.logger.debug(\"DBStore.jwk_load() ended with: %s\", jwk_dict)\n        return jwk_dict\n\n    def cli_permissions_get(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad cliaccount information and build jwk key dictionary\"\"\"\n        self.logger.debug(\"DBStore.cli_jwk_load(%s)\", aname)\n        account_list = self._cliaccount_search(\"name\", aname)\n        account_dic = {}\n        if account_list:\n            account_dic = {\n                \"cliadmin\": account_list[\"cliadmin\"],\n                \"reportadmin\": account_list[\"reportadmin\"],\n                \"certificateadmin\": account_list[\"certificateadmin\"],\n            }\n\n        return account_dic\n\n    def db_update(self):\n        \"\"\"update database\"\"\"\n        self.logger.debug(\"DBStore.db_update()\")\n        self._db_open()\n\n        # update certificate table\n        self._db_update_certificate()\n\n        # update status table\n        self._db_update_status()\n\n        # update challenge table\n        self._db_update_challenge()\n\n        # update account table\n        self._db_update_account()\n\n        # update order table\n        self._db_update_orders()\n\n        # update authorization table\n        self._db_update_authorization()\n\n        # create housekeeping table\n        self._db_update_housekeeping()\n\n        # create ca_handler table\n        self._db_update_cahandler()\n\n        # create cliaccount table\n        self._db_update_cliaccount()\n\n        # version update\n        self.logger.info(f\"update dbversion to {__dbversion__}\")\n        self.cursor.execute(\n            f\"\"\"INSERT OR IGNORE INTO housekeeping (name, value) VALUES (\"dbversion\", \"{__dbversion__}\")\"\"\"\n        )\n        self.cursor.execute(\n            f'''UPDATE housekeeping SET value = \"{__dbversion__}\" WHERE name=\"dbversion\"'''\n        )\n\n        self._db_close()\n        self.logger.debug(\"DBStore.db_update() ended\")\n\n    def dbversion_get(self) -> Tuple[List[str], str]:\n        \"\"\"get db version from housekeeping table\"\"\"\n        self.logger.debug(\"DBStore.dbversion_get()\")\n        self._db_open()\n        pre_statement = \"SELECT value from housekeeping WHERE housekeeping.name LIKE ?\"\n        self.cursor.execute(pre_statement, [\"dbversion\"])\n        query = list(self.cursor.fetchone())\n        if query:\n            result = query[0]\n        else:\n            self.logger.error(\"DBStore.dbversion_get() lookup failed\")\n            result = None\n        self._db_close()\n        self.logger.debug(\"DBStore.dbversion_get() ended with %s\", result)\n        return (result, \"tools/db_update.py\")\n\n    def hkparameter_add(self, data_dic: Dict[str, str]) -> Tuple[str, bool]:\n        \"\"\"add housekeeping paramter to database\"\"\"\n        # we need this for compability with django\n        created = False\n        # check if we alredy have an entry for the key\n        exists = self.hkparameter_get(data_dic[\"name\"])\n        self._db_open()\n        if bool(exists):\n            # update\n            self.logger.debug(f'parameter exists: {data_dic[\"name\"]}')\n            self.cursor.execute(\n                \"\"\"UPDATE HOUSEKEEPING SET name = :name, value = :value WHERE name = :name\"\"\",\n                data_dic,\n            )\n        else:\n            # insert\n            self.cursor.execute(\n                \"\"\"INSERT INTO HOUSEKEEPING(name, value) VALUES(:name, :value)\"\"\",\n                data_dic,\n            )\n            created = True\n\n        self._db_close()\n        self.logger.debug(\"DBStore.account_add() ended\")\n        return (data_dic[\"name\"], created)\n\n    def hkparameter_get(self, parameter: str) -> List[str]:\n        \"\"\"get parameter from housekeeping table\"\"\"\n        self.logger.debug(\"DBStore.hkparameter_get()\")\n        self._db_open()\n        pre_statement = \"SELECT value from housekeeping WHERE housekeeping.name LIKE ?\"\n        self.cursor.execute(pre_statement, [parameter])\n        try:\n            query = list(self.cursor.fetchone())\n        except Exception:\n            query = None\n\n        if query:\n            result = query[0]\n        else:\n            result = None\n        self._db_close()\n        self.logger.debug(\"DBStore.hkparameter_get() ended with %s\", result)\n        return result\n\n    def jwk_load(self, aname: str) -> Dict[str, str]:\n        \"\"\"looad account informatino and build jwk key dictionary\"\"\"\n        self.logger.debug(\"DBStore.jwk_load(%s)\", aname)\n        account_list = self._account_search(\"name\", aname)\n        jwk_dict = {}\n        if account_list:\n            jwk_dict = json.loads(account_list[3])\n            jwk_dict[\"alg\"] = account_list[2]\n        self.logger.debug(\"DBStore.jwk_load() ended with: %s\", jwk_dict)\n        return jwk_dict\n\n    def nonce_add(self, nonce: str) -> int:\n        \"\"\"check if nonce is in datbase\n        in: nonce\n        return: rowid\"\"\"\n        self.logger.debug(\"DBStore.nonce_add(%s)\", nonce)\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"INSERT INTO nonce(nonce) VALUES(:nonce)\"\"\", {\"nonce\": nonce}\n        )\n        rid = self.cursor.lastrowid\n        self._db_close()\n        self.logger.debug(\"DBStore.nonce_add() ended\")\n        return rid\n\n    def nonce_check(self, nonce: str) -> bool:\n        \"\"\"ceck if nonce is in datbase\n        in: nonce\n        return: true in case nonce exit, otherwise false\"\"\"\n        self.logger.debug(\"DBStore.nonce_check(%s)\", nonce)\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"SELECT nonce FROM nonce WHERE nonce=:nonce\"\"\", {\"nonce\": nonce}\n        )\n        result = bool(self.cursor.fetchone())\n        self._db_close()\n        self.logger.debug(\"DBStore.nonce_check() ended\")\n        return result\n\n    def nonce_delete(self, nonce: str):\n        \"\"\"delete nonce from datbase\n        in: nonce\"\"\"\n        self.logger.debug(\"DBStore.nonce_delete(%s)\", nonce)\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"DELETE FROM nonce WHERE nonce=:nonce\"\"\", {\"nonce\": nonce}\n        )\n        self._db_close()\n        self.logger.debug(\"DBStore.nonce_delete() ended\")\n\n    def order_add(self, data_dic: Dict[str, str]) -> int:\n        \"\"\"add order to database\"\"\"\n        self.logger.debug(\"DBStore.order_add(%s)\", data_dic)\n        if \"notbefore\" not in data_dic:\n            data_dic[\"notbefore\"] = \"\"\n\n        if \"notafter\" not in data_dic:\n            data_dic[\"notafter\"] = \"\"\n\n        if \"profile\" not in data_dic:\n            data_dic[\"profile\"] = \"\"\n\n        account = self.account_lookup(\"name\", data_dic[\"account\"])\n        if account:\n            data_dic[\"account\"] = account[\"id\"]\n            self._db_open()\n            self.cursor.execute(\n                \"\"\"INSERT INTO orders(name, identifiers, account_id, status_id, expires, notbefore, notafter, profile) VALUES(:name, :identifiers, :account, :status, :expires, :notbefore, :notafter, :profile )\"\"\",\n                data_dic,\n            )\n            rid = self.cursor.lastrowid\n            self._db_close()\n        else:\n            rid = None\n        self.logger.debug(\"DBStore.order_add() ended\")\n        return rid\n\n    def order_lookup(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\n            \"notbefore\",\n            \"notafter\",\n            \"identifiers\",\n            \"expires\",\n            \"status__name\",\n        ),\n    ) -> Dict[str, str]:\n        \"\"\"search orders for a given ordername\"\"\"\n        self.logger.debug(\"order_lookup(%s:%s)\", column, string)\n\n        try:\n            lookup = dict_from_row(self._order_search(column, string))\n        except Exception:\n            lookup = None\n\n        result = {}\n        if lookup:\n            # small hack (not sure db returnsblank and not 0)\n            if lookup[\"notafter\"] == \"\":\n                lookup[\"notafter\"] = 0\n            if lookup[\"notbefore\"] == \"\":\n                lookup[\"notbefore\"] = 0\n            for ele in vlist:\n                if ele == \"status__name\":\n                    result[\"status\"] = lookup[\"status__name\"]\n                else:\n                    result[ele] = lookup[ele]\n\n        self.logger.debug(\"DBStore.order_lookup() ended with: %s\", result)\n        return result\n\n    def order_update(self, data_dic: Dict[str, str]):\n        \"\"\"update order\"\"\"\n        self.logger.debug(\"order_update(%s)\", data_dic)\n        if \"status\" in data_dic:\n            data_dic[\"status\"] = dict_from_row(\n                self._status_search(\"name\", data_dic[\"status\"])\n            )[\"id\"]\n        self._db_open()\n        self.cursor.execute(\n            \"\"\"UPDATE orders SET status_id = :status WHERE name = :name\"\"\", data_dic\n        )\n        self._db_close()\n        self.logger.debug(\"DBStore.order_update() ended\")\n\n    def orders_invalid_search(\n        self,\n        column: str,\n        string: str,\n        vlist: List[str] = (\n            \"id\",\n            \"name\",\n            \"expires\",\n            \"identifiers\",\n            \"created_at\",\n            \"status__id\",\n            \"status__name\",\n            \"account__id\",\n            \"account__name\",\n            \"account__contact\",\n        ),\n        operant=\"LIKE\",\n    ) -> List[str]:\n        \"\"\"search order table for a certain key/value pair\"\"\"\n        self.logger.debug(\n            \"DBStore.orders_invalid_search(column:%s, pattern:%s)\", column, string\n        )\n        if not self._identifier_check(\"orders\", column):\n            self.logger.warning(COLUMN_NOT_IN_TABLE_MSG, column, \"orders\")\n            return []\n\n        self._db_open()\n        pre_statement = f\"\"\"SELECT\n                                orders.*,\n                                status.name as status__name,\n                                status.id as status__id,\n                                account.name as account__name,\n                                account.contact as account__contact,\n                                account.id as account__id\n                                FROM orders\n                            LEFT JOIN status on status.id = orders.status_id\n                            LEFT JOIN account on account.id = orders.account_id\n                            WHERE orders.status_id > 1 AND orders.{column} {operant} ?\"\"\"\n        self.cursor.execute(pre_statement, [string])\n        rows = self.cursor.fetchall()\n        order_list = []\n        for row in rows:\n            lookup = dict_from_row(row)\n            result = {}\n            if lookup:\n                for ele in vlist:\n                    result[ele] = lookup[ele]\n            order_list.append(result)\n\n        self._db_close()\n        self.logger.debug(\"DBStore.orders_invalid_search() ended\")\n        return order_list\n"
  },
  {
    "path": "examples/django/acme2certifier/__init__.py",
    "content": ""
  },
  {
    "path": "examples/django/acme2certifier/settings.py",
    "content": "\"\"\"\nDjango settings for acme2certifier project\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\nTBR = \"TO BE REPLACED\"\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = TBR\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"127.0.0.1\"]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"acme_srv\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nROOT_URLCONF = \"acme2certifier.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \"acme2certifier.wsgi.application\"\n\n\n# Database\n# https://docs.djangoproject.com/en/1.11/ref/settings/#databases\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"acme2certifier\",\n        \"USER\": \"acme2certifier\",\n        \"PASSWORD\": TBR,\n        \"HOST\": TBR,\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1\",\n            \"charset\": \"utf8mb4\",\n            \"use_unicode\": True,\n        },\n    },\n}\n\n# Password validation\n# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/1.11/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/1.11/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.AutoField\"\n"
  },
  {
    "path": "examples/django/acme2certifier/urls.py",
    "content": "\"\"\"acme2certifier URL Configuration\"\"\"\n\nfrom django.urls import include, re_path\nfrom django.contrib import admin\nfrom acme_srv import views\nfrom acme_srv.helper import load_config\nfrom django.views.generic import RedirectView\n\n# load config to set url_prefix\nCONFIG = load_config()\n\n# check ifwe need to prefix the url\nif \"Directory\" in CONFIG and \"url_prefix\" in CONFIG[\"Directory\"]:\n    PREFIX = CONFIG[\"Directory\"][\"url_prefix\"] + \"/\"\n    if PREFIX.startswith(\"/\"):\n        PREFIX = PREFIX.lstrip(\"/\")\nelse:\n    PREFIX = \"\"\n\nurlpatterns = [\n    re_path(r\"^admin/\", admin.site.urls),\n    re_path(r\"^$\", RedirectView.as_view(url=\"/directory\")),\n    re_path(r\"^directory$\", views.directory, name=\"directory\"),\n    re_path(rf\"^{PREFIX}get_servername$\", views.servername_get, name=\"servername_get\"),\n    re_path(rf\"^{PREFIX}trigger$\", views.trigger, name=\"trigger\"),\n    re_path(rf\"^{PREFIX}housekeeping$\", views.housekeeping, name=\"housekeeping\"),\n    re_path(rf\"^{PREFIX}acme/\", include(\"acme_srv.urls\")),\n]\n\n# check if we need to activate the url pattern for challenge verification\nif \"CAhandler\" in CONFIG and \"acme_url\" in CONFIG[\"CAhandler\"]:\n    urlpatterns.append(\n        re_path(\n            rf\"^{PREFIX}.well-known/acme-challenge/\",\n            views.acmechallenge_serve,\n            name=\"acmechallenge_serve\",\n        )\n    )\n"
  },
  {
    "path": "examples/django/acme2certifier/wsgi.py",
    "content": "\"\"\"\nWSGI config for acme2certifier project.\n\"\"\"\n\n# pylint: disable=C0413\nimport os\nimport sys\n\nPROJECT_HOME = \"/var/www/acme2certifier\"\n\nif PROJECT_HOME not in sys.path:\n    sys.path.append(PROJECT_HOME)\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"acme2certifier.settings\")\n\nfrom django.core.wsgi import get_wsgi_application  # nopep8\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "examples/django/acme_srv/__init__.py",
    "content": ""
  },
  {
    "path": "examples/django/acme_srv/a2c_response.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"customized class for django json response\"\"\"\nimport json\nfrom django.http import HttpResponse\nfrom django.core.serializers.json import DjangoJSONEncoder\n\n\nclass JsonResponse(HttpResponse):\n    \"\"\"\n    An HTTP response class that consumes data to be serialized to JSON.\n    and changes the contentent type base do status code\n    \"\"\"\n\n    def __init__(\n        self,\n        data,\n        encoder=DjangoJSONEncoder,\n        safe=True,\n        json_dumps_params=None,\n        **kwargs\n    ):\n        if safe and not isinstance(data, dict):\n            raise TypeError(\n                \"In order to allow non-dict objects to be serialized set the \"\n                \"safe parameter to False.\"\n            )\n        if json_dumps_params is None:\n            json_dumps_params = {}\n\n        if \"status\" in kwargs and kwargs[\"status\"] > 201:\n            kwargs.setdefault(\"content_type\", \"application/problem+json\")\n        else:\n            kwargs.setdefault(\"content_type\", \"application/json\")\n\n        data = json.dumps(data, cls=encoder, **json_dumps_params)\n        super().__init__(content=data, **kwargs)\n"
  },
  {
    "path": "examples/django/acme_srv/admin.py",
    "content": "\"\"\"admin.py for django project\"\"\"\n\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\n# Register your models here.\n"
  },
  {
    "path": "examples/django/acme_srv/fixture/__init__.py",
    "content": ""
  },
  {
    "path": "examples/django/acme_srv/fixture/status.yaml",
    "content": "-   model: acme_srv.status\n    pk: 1\n    fields:\n        name: invalid\n-   model: acme_srv.status\n    pk: 2\n    fields:\n        name: pending\n-   model: acme_srv.status\n    pk: 3\n    fields:\n        name: ready\n-   model: acme_srv.status\n    pk: 4\n    fields:\n        name: processing\n-   model: acme_srv.status\n    pk: 5\n    fields:\n        name: valid\n-   model: acme_srv.status\n    pk: 6\n    fields:\n        name: expired\n-   model: acme_srv.status\n    pk: 7\n    fields:\n        name: deactivated\n-   model: acme_srv.status\n    pk: 8\n    fields:\n        name: revoked\n-   model: acme_srv.housekeeping\n    pk: 1\n    fields:\n        name: dbversion\n        value: \"0.41\"\n"
  },
  {
    "path": "examples/django/acme_srv/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "examples/django/acme_srv/models.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"model for acme django database\"\"\"\nfrom __future__ import unicode_literals\nfrom django.db import models\n\n\n# Create your models here.\nclass Nonce(models.Model):\n    \"\"\"nonce table\"\"\"\n\n    nonce = models.CharField(max_length=50)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n    def __unicode__(self):\n        return self.nonce\n\n\nclass Status(models.Model):\n    \"\"\"order status\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n\n    def __unicode__(self):\n        return self.name\n\n\nclass Account(models.Model):\n    \"\"\"account table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    jwk = models.TextField(blank=True)\n    alg = models.CharField(max_length=10)\n    contact = models.CharField(max_length=255)\n    eab_kid = models.TextField(max_length=255, blank=True)\n    created_at = models.DateTimeField(auto_now_add=True)\n    status = models.ForeignKey(Status, default=5, on_delete=models.CASCADE)\n\n    def __unicode__(self):\n        return self.contact\n\n\nclass Cliaccount(models.Model):\n    \"\"\"account table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    jwk = models.TextField(blank=True)\n    contact = models.CharField(max_length=255)\n    reportadmin = models.BooleanField(default=False)\n    cliadmin = models.BooleanField(default=False)\n    certificateadmin = models.BooleanField(default=False)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n\nclass Order(models.Model):\n    \"\"\"order table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    account = models.ForeignKey(Account, on_delete=models.CASCADE)\n    notbefore = models.IntegerField(default=0)\n    notafter = models.IntegerField(default=0)\n    identifiers = models.TextField()\n    profile = models.TextField(blank=True)\n    status = models.ForeignKey(Status, default=2, on_delete=models.CASCADE)\n    expires = models.IntegerField(default=0)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n    def __unicode__(self):\n        return self.name\n\n\nclass Authorization(models.Model):\n    \"\"\"order table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    order = models.ForeignKey(Order, on_delete=models.CASCADE)\n    type = models.CharField(max_length=5)\n    value = models.TextField()\n    token = models.CharField(max_length=64, blank=True)\n    expires = models.IntegerField(default=0)\n    status = models.ForeignKey(Status, default=1, on_delete=models.CASCADE)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n    def __unicode__(self):\n        return self.name\n\n\nclass Challenge(models.Model):\n    \"\"\"order table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    authorization = models.ForeignKey(Authorization, on_delete=models.CASCADE)\n    type = models.CharField(max_length=15)\n    token = models.CharField(max_length=64)\n    expires = models.IntegerField(default=0)\n    status = models.ForeignKey(Status, default=2, on_delete=models.CASCADE)\n    created_at = models.DateTimeField(auto_now_add=True)\n    keyauthorization = models.CharField(max_length=128, blank=True)\n    source = models.CharField(max_length=128, blank=True)\n    validated = models.IntegerField(default=0)\n    validation_error = models.TextField(blank=True)\n\n    def __unicode__(self):\n        return self.name\n\n\nclass Certificate(models.Model):\n    \"\"\"order table\"\"\"\n\n    name = models.CharField(max_length=15, unique=True)\n    order = models.ForeignKey(Order, on_delete=models.CASCADE)\n    csr = models.TextField(null=True, blank=True)  # NOSONAR\n    cert = models.TextField(null=True, blank=True)  # NOSONAR\n    cert_raw = models.TextField(null=True, blank=True)  # NOSONAR\n    error = models.TextField(null=True, blank=True)  # NOSONAR\n    poll_identifier = models.TextField(null=True, blank=True)  # NOSONAR\n    expire_uts = models.IntegerField(default=0)\n    issue_uts = models.IntegerField(default=0)\n    renewal_info = models.TextField(null=True, blank=True)  # NOSONAR\n    aki = models.TextField(null=True, blank=True)  # NOSONAR\n    serial = models.TextField(null=True, blank=True)  # NOSONAR\n    replaced = models.BooleanField(default=False)\n    header_info = models.TextField(null=True, blank=True)  # NOSONAR\n    created_at = models.DateTimeField(auto_now_add=True, null=True)  # NOSONAR\n\n    def __unicode__(self):\n        return self.name\n\n\nclass Housekeeping(models.Model):\n    \"\"\"housekeeping\"\"\"\n\n    name = models.CharField(max_length=30, unique=True)\n    value = models.CharField(max_length=30, blank=True)\n    modified_at = models.DateTimeField(\"value\", auto_now_add=True, null=True)\n\n\nclass Cahandler(models.Model):\n    \"\"\"housekeeping\"\"\"\n\n    name = models.CharField(max_length=50, unique=True)\n    value1 = models.CharField(max_length=250, blank=True)\n    value2 = models.CharField(max_length=250, blank=True)\n    created_at = models.DateTimeField(\"value\", auto_now_add=True, null=True)\n"
  },
  {
    "path": "examples/django/acme_srv/tests.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"tester\"\"\"\nfrom __future__ import unicode_literals\n\n# Create your tests here.\n"
  },
  {
    "path": "examples/django/acme_srv/urls.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"urls for acme django database\"\"\"\nfrom django.urls import re_path\nfrom acme_srv import views\n\nurlpatterns = [\n    re_path(r\"^acct\", views.acct, name=\"acct\"),\n    re_path(r\"^authz\", views.authz, name=\"authz\"),\n    re_path(r\"^cert\", views.cert, name=\"cert\"),\n    re_path(r\"^chall\", views.chall, name=\"chall\"),\n    re_path(r\"^directory$\", views.directory, name=\"directory\"),\n    re_path(r\"^newaccount$\", views.newaccount, name=\"newaccount\"),\n    re_path(r\"^key-change$\", views.acct, name=\"acct\"),\n    re_path(r\"^newnonce$\", views.newnonce, name=\"newnonce\"),\n    re_path(r\"^neworders$\", views.neworders, name=\"neworders\"),\n    re_path(r\"^order\", views.order, name=\"order\"),\n    re_path(r\"^revokecert\", views.revokecert, name=\"revokecert\"),\n    re_path(r\"^renewal-info\", views.renewalinfo, name=\"renewalinfo\"),\n    re_path(r\"^servername_get$\", views.servername_get, name=\"servername_get\"),\n]\n"
  },
  {
    "path": "examples/django/acme_srv/views.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"acme app main view\"\"\"\nfrom __future__ import unicode_literals, print_function\nfrom django.http import HttpResponse, HttpResponseNotFound\nfrom django.utils.html import escape\nfrom acme_srv.a2c_response import JsonResponse\nfrom acme_srv.authorization import Authorization\nfrom acme_srv.account import Account\nfrom acme_srv.certificate import Certificate\nfrom acme_srv.challenge import Challenge\nfrom acme_srv.directory import Directory\nfrom acme_srv.helper import (\n    get_url,\n    load_config,\n    logger_setup,\n    logger_info,\n    config_check,\n)\nfrom acme_srv.housekeeping import Housekeeping\nfrom acme_srv.nonce import Nonce\nfrom acme_srv.order import Order\nfrom acme_srv.renewalinfo import Renewalinfo\nfrom acme_srv.trigger import Trigger\nfrom acme_srv.version import __dbversion__, __version__\nfrom acme_srv.acmechallenge import Acmechallenge\n\n# load config to set debug mode\nCONFIG = load_config()\nDEBUG = CONFIG.getboolean(\"DEFAULT\", \"debug\", fallback=False)\n\n# initialize logger\nLOGGER = logger_setup(DEBUG)\nLOGGER.info(\"starting acme2certifier version %s\", __version__)\n\nMETHOD_NOT_ALLOWED = \"Method Not Allowed\"\nERR_DATA_POST = {\n    \"status\": 405,\n    \"message\": METHOD_NOT_ALLOWED,\n    \"detail\": \"Wrong request type. Expected POST.\",\n}\nERR_RESPONSE_POST = JsonResponse(status=405, data=ERR_DATA_POST)\nERR_RESPONSE_HEAD_GET = JsonResponse(\n    status=400,\n    data={\n        \"status\": 405,\n        \"message\": METHOD_NOT_ALLOWED,\n        \"detail\": \"Wrong request type. Expected HEAD or GET.\",\n    },\n)\n\n# check configuration for parameters masked in \"\"\nconfig_check(LOGGER, CONFIG)\n\nwith Housekeeping(DEBUG, LOGGER) as version_check:\n    version_check.dbversion_check(__dbversion__)\n\n\ndef handle_exception(exc_type, exc_value, exc_traceback):\n    \"\"\"exception handler\"\"\"\n    print(\"My Error Information\")\n    print(\"Type:\", exc_type)\n    print(\"Value:\", exc_value)\n    print(\"Traceback:\", exc_traceback)\n\n\ndef pretty_request(request):\n    \"\"\"print request details for debugging\"\"\"\n    headers = \"\"\n    for header, value in request.META.items():\n        if not header.startswith(\"HTTP\"):\n            continue\n        header = \"-\".join([h.capitalize() for h in header[5:].lower().split(\"_\")])\n        headers += f\"{header}: {value}\\n\"\n\n    return (\n        f\"{request.method} HTTP/1.1\\n\"\n        f'Content-Length: {request.META[\"CONTENT_LENGTH\"]}\\n'\n        f'Content-Type: {request.META[\"CONTENT_TYPE\"]}\\n'\n        f\"{headers}\\n\\n\"\n        f\"{request.body}\"\n    )\n\n\ndef directory(request):\n    \"\"\"get directory\"\"\"\n    with Directory(DEBUG, get_url(request.META), LOGGER) as cfg_dir:\n        response = cfg_dir.directory_get()\n        if \"error\" in response:\n            return JsonResponse(\n                status=403,\n                data={\n                    \"status\": 403,\n                    \"message\": \"Forbidden\",\n                    \"detail\": response[\"error\"],\n                },\n            )\n        else:\n            return JsonResponse(cfg_dir.directory_get())\n\n\ndef newaccount(request):\n    \"\"\"new account\"\"\"\n    if request.method == \"POST\":\n        with Account(DEBUG, get_url(request.META), LOGGER) as account:\n            response_dic = account.new(request.body)\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef newnonce(request):\n    \"\"\"new nonce\"\"\"\n    if request.method in [\"HEAD\", \"GET\"]:\n        with Nonce(DEBUG, LOGGER) as nonce:\n            if request.method == \"HEAD\":\n                response = HttpResponse(\"\")\n            else:\n                response = HttpResponse(status=204)\n            # generate nonce\n            response[\"Replay-Nonce\"] = nonce.generate_and_add()\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                {\"header\": {\"Replay-Nonce\": response[\"Replay-Nonce\"]}},\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_HEAD_GET\n\n\ndef servername_get(request):\n    \"\"\"get server name\"\"\"\n    with Directory(DEBUG, get_url(request.META), LOGGER) as cfg_dir:\n        return JsonResponse({\"server_name\": escape(cfg_dir.servername_get())})\n\n\ndef acct(request):\n    \"\"\"xxxx command\"\"\"\n    with Account(DEBUG, get_url(request.META), LOGGER) as account:\n        response_dic = account.parse(request.body)\n        # create the response\n        response = JsonResponse(status=response_dic[\"code\"], data=response_dic[\"data\"])\n\n        # generate additional header elements\n        for element in response_dic[\"header\"]:\n            response[element] = response_dic[\"header\"][element]\n\n        # logging\n        logger_info(\n            LOGGER, request.META[\"REMOTE_ADDR\"], request.META[\"PATH_INFO\"], response_dic\n        )\n        # send response\n        return response\n\n\ndef neworders(request):\n    \"\"\"new account\"\"\"\n    if request.method == \"POST\":\n        with Order(DEBUG, get_url(request.META), LOGGER) as norder:\n            response_dic = norder.new(request.body)\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            if \"Replay-Nonce\" not in response:\n                response[\"Replay-Nonce\"] = \"\"\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                {\"header\": {\"Replay-Nonce\": response[\"Replay-Nonce\"]}},\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef authz(request):\n    \"\"\"new-authz command\"\"\"\n    if request.method in (\"POST\", \"GET\"):\n        with Authorization(DEBUG, get_url(request.META), LOGGER) as authorization:\n            if request.method == \"POST\":\n                response_dic = authorization.new_post(request.body)\n            else:\n                response_dic = authorization.new_get(request.build_absolute_uri())\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef chall(request):\n    \"\"\"challenge command\"\"\"\n    with Challenge(\n        debug=DEBUG,\n        srv_name=get_url(request.META),\n        source=request.META[\"REMOTE_ADDR\"],\n        logger=LOGGER,\n    ) as challenge:\n        # pylint: disable=R1705\n        if request.method == \"POST\":\n            response_dic = challenge.parse(request.body)\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n        elif request.method == \"GET\":\n            response_dic = challenge.get(request.build_absolute_uri())\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n\n            # send response\n            return response\n        else:\n            return ERR_RESPONSE_POST\n\n\ndef order(request):\n    \"\"\"order request\"\"\"\n    if request.method == \"POST\":\n        with Order(DEBUG, get_url(request.META), LOGGER) as eorder:\n            response_dic = eorder.parse(request.body, request.META)\n            # create the response\n            response = JsonResponse(\n                status=response_dic[\"code\"], data=response_dic[\"data\"]\n            )\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef cert(request):\n    \"\"\"cert request\"\"\"\n    if request.method in (\"POST\", \"GET\"):\n        with Certificate(DEBUG, get_url(request.META), LOGGER) as certificate:\n            if request.method == \"POST\":\n                response_dic = certificate.new_post(request.body)\n            else:\n                response_dic = certificate.new_get(request.build_absolute_uri())\n\n            # create the response\n            if response_dic[\"code\"] == 200:\n                response = HttpResponse(response_dic[\"data\"])\n                # generate additional header elements\n                for element in response_dic[\"header\"]:\n                    response[element] = response_dic[\"header\"][element]\n            else:\n                response = HttpResponse(status=response_dic[\"code\"])\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef revokecert(request):\n    \"\"\"cert revocation\"\"\"\n    if request.method == \"POST\":\n        with Certificate(DEBUG, get_url(request.META), LOGGER) as certificate:\n            response_dic = certificate.revoke(request.body)\n            # create the response\n            if \"data\" in response_dic:\n                response = JsonResponse(\n                    status=response_dic[\"code\"], data=response_dic[\"data\"]\n                )\n            else:\n                response = HttpResponse(status=response_dic[\"code\"])\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef trigger(request):\n    \"\"\"ca trigger\"\"\"\n    if request.method == \"POST\":\n        with Trigger(DEBUG, get_url(request.META), LOGGER) as trigger_:\n            response_dic = trigger_.parse(request.body)\n            # create the response\n            if \"data\" in response_dic:\n                response = JsonResponse(\n                    status=response_dic[\"code\"], data=response_dic[\"data\"]\n                )\n            else:\n                response = HttpResponse(status=response_dic[\"code\"])\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n            # send response\n            return response\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef renewalinfo(request):\n    \"\"\"renewal info\"\"\"\n    if request.method in (\"POST\", \"GET\"):\n        with Renewalinfo(DEBUG, get_url(request.META), LOGGER) as renewalinfo_:\n            if request.method == \"POST\":\n                response_dic = renewalinfo_.update(request.body)\n            else:\n                response_dic = renewalinfo_.get(request.build_absolute_uri())\n\n            # create the response\n            if response_dic[\"code\"] == 200 and request.method == \"GET\":\n                response = JsonResponse(response_dic[\"data\"])\n                # generate additional header elements\n                for element in response_dic[\"header\"]:\n                    response[element] = response_dic[\"header\"][element]\n            else:\n                response = HttpResponse(status=response_dic[\"code\"])\n\n            # logging\n            logger_info(\n                LOGGER,\n                request.META[\"REMOTE_ADDR\"],\n                request.META[\"PATH_INFO\"],\n                response_dic,\n            )\n\n            # send response\n            return response\n\n    else:\n        return ERR_RESPONSE_POST\n\n\ndef housekeeping(request):\n    \"\"\"ca trigger\"\"\"\n    if request.method == \"POST\":\n        with Housekeeping(DEBUG, LOGGER) as housekeeping_:\n            response_dic = housekeeping_.parse(request.body)\n            # create the response\n            if \"data\" in response_dic:\n                response = JsonResponse(\n                    status=response_dic[\"code\"], data=response_dic[\"data\"], safe=False\n                )\n            else:\n                response = HttpResponse(status=response_dic[\"code\"])\n\n            # generate additional header elements\n            for element in response_dic[\"header\"]:\n                response[element] = response_dic[\"header\"][element]\n\n            # logging\n            logger_info(\n                LOGGER, request.META[\"REMOTE_ADDR\"], request.META[\"PATH_INFO\"], \"****\"\n            )\n            # send response\n            return response\n    else:\n        return JsonResponse(status=405, data=ERR_DATA_POST, safe=False)\n\n\ndef acmechallenge_serve(request):\n    \"\"\"serving acme challenges\"\"\"\n    with Acmechallenge(DEBUG, get_url(request.META), LOGGER) as acmechallenge:\n        key_authorization = acmechallenge.lookup(request.META[\"PATH_INFO\"])\n        # pylint:  disable=R1705\n        if key_authorization:\n            return HttpResponse(key_authorization)\n        else:\n            return HttpResponseNotFound(\"NOT FOUND\")\n"
  },
  {
    "path": "examples/django/manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"acme2certifier.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError:\n        # The above import may fail for some other reason. Ensure that the\n        # issue is really that Django is missing to avoid masking other\n        # exceptions on Python 2.\n        try:\n            import django  # lgtm [py/unused-import]\n        except ImportError:\n            raise ImportError(\n                \"Couldn't import Django. Are you sure it's installed and \"\n                \"available on your PYTHONPATH environment variable? Did you \"\n                \"forget to activate a virtual environment?\"\n            )\n        raise\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "examples/eab_handler/file_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"eab file handler\"\"\"\nfrom __future__ import print_function\nfrom typing import Dict\nimport csv\n\n# pylint: disable=E0401\nfrom acme_srv.helper import load_config\n\n\nclass EABhandler(object):\n    \"\"\"EAB file handler\"\"\"\n\n    def __init__(self, logger: object = None):\n        self.logger = logger\n        self.key_file = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        if not self.key_file:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"EABhandler\")\n        self.key_file = config_dic.get(\"EABhandler\", \"key_file\", fallback=self.key_file)\n\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def key_file_load(self) -> Dict[str, str]:\n        \"\"\"load key_file\"\"\"\n        self.logger.debug(\"EABhandler.key_file_load()\")\n\n        data_dic = {}\n        if self.key_file:\n            try:\n                with open(self.key_file, mode=\"r\", encoding=\"utf8\") as csv_file:\n                    csv_reader = csv.DictReader(csv_file)\n                    for row in csv_reader:\n                        data_dic[row[\"eab_kid\"]] = row[\"eab_mac\"]\n            except Exception as err:\n                self.logger.error(\"Failed to load EAB key file: %s\", err)\n\n        self.logger.debug(\"EABhandler.key_file_load() ended: {%s}\", bool(data_dic))\n        return data_dic\n\n    def mac_key_get(self, kid: str = None) -> str:\n        \"\"\"check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get(%s)\", kid)\n\n        mac_key = None\n        if self.key_file and kid:\n            data_dic = self.key_file_load()\n            if kid in data_dic:\n                mac_key = data_dic[kid]\n            else:\n                self.logger.error(\n                    \"MAC key retrieval failed: kid '%s' not found in key file.\", kid\n                )\n        self.logger.debug(\"EABhandler.mac_key_get() ended with: %s\", bool(mac_key))\n        return mac_key\n"
  },
  {
    "path": "examples/eab_handler/json_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"eab json handler\"\"\"\nfrom __future__ import print_function\nimport json\nfrom typing import Dict\n\n# pylint: disable=C0209, E0401\nfrom acme_srv.helper import load_config\n\n\nclass EABhandler(object):\n    \"\"\"EAB file handler\"\"\"\n\n    def __init__(self, logger: object = None):\n        self.logger = logger\n        self.key_file = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        if not self.key_file:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"EABhandler\")\n        self.key_file = config_dic.get(\"EABhandler\", \"key_file\", fallback=self.key_file)\n\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def key_file_load(self) -> Dict[str, str]:\n        \"\"\"load key_file\"\"\"\n        self.logger.debug(\"EABhandler.key_file_load()\")\n\n        data_dic = {}\n        if self.key_file:\n            try:\n                with open(self.key_file, encoding=\"utf8\") as json_file:\n                    data_dic = json.load(json_file)\n            except Exception as err:\n                self.logger.error(\"Failed to load EAB key file: %s\", err)\n\n        self.logger.debug(\n            \"EABhandler.key_file_load() ended: {0}\".format(bool(data_dic))\n        )\n        return data_dic\n\n    def mac_key_get(self, kid: str = None) -> str:\n        \"\"\"check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get({})\".format(kid))\n        mac_key = None\n\n        data_dic = self.key_file_load()\n        if kid and kid in data_dic:\n            mac_key = data_dic[kid]\n\n        self.logger.debug(\n            \"EABhandler.mac_key_get() ended with: {0}\".format(bool(mac_key))\n        )\n        return mac_key\n"
  },
  {
    "path": "examples/eab_handler/key_file.csv",
    "content": "eab_kid,eab_mac\nkeyid_00,V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\nkeyid_01,YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\nkeyid_02,dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\nkeyid_03,YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\n"
  },
  {
    "path": "examples/eab_handler/key_file.json",
    "content": "{\n  \"keyid_00\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n  \"keyid_01\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n  \"keyid_02\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n  \"keyid_03\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\"\n}\n"
  },
  {
    "path": "examples/eab_handler/kid_profile_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"eab json handler\"\"\"\nfrom __future__ import print_function\nimport yaml\nimport json\nimport re\nfrom typing import List, Tuple\n\n# pylint: disable=C0209, E0401\nfrom acme_srv.helper import load_config, csr_cn_get, csr_san_get\n\n\nclass EABhandler(object):\n    \"\"\"EAB file handler\"\"\"\n\n    def __init__(self, logger: object = None):\n        self.logger = logger\n        self.key_file = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        if not self.key_file:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"EABhandler\")\n        self.key_file = config_dic.get(\"EABhandler\", \"key_file\", fallback=self.key_file)\n\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def _chk_san_lists_get(self, csr: str) -> Tuple[List[str], List[bool]]:\n        \"\"\"check lists\"\"\"\n        self.logger.debug(\"EABhandler._chk_san_lists_get()\")\n\n        # get sans and build a list\n        _san_list = csr_san_get(self.logger, csr)\n\n        check_list = []\n        san_list = []\n\n        if _san_list:\n            for san in _san_list:\n                try:\n                    # SAN list must be modified/filtered)\n                    (_san_type, san_value) = san.lower().split(\":\")\n                    san_list.append(san_value)\n                except Exception:\n                    # force check to fail as something went wrong during parsing\n                    check_list.append(False)\n                    self.logger.info(\n                        \"SAN list parsing failed at entry: {0}\".format(san)\n                    )\n\n        self.logger.debug(\"EABhandler._chk_san_lists_get() ended\")\n        return (san_list, check_list)\n\n    def _cn_add(self, csr: str, san_list: List[str]) -> Tuple[List[str], str]:\n        \"\"\"add CN if required\"\"\"\n        self.logger.debug(\"EABhandler._cn_add()\")\n\n        # get common name and attach it to san_list\n        cn_ = csr_cn_get(self.logger, csr)\n\n        if cn_:\n            cn_ = cn_.lower()\n            if cn_ not in san_list:\n                # append cn to san_list\n                self.logger.debug(\"EABhandler._csr_check(): append cn to san_list\")\n                san_list.append(cn_)\n\n        self.logger.debug(\"EABhandler._cn_add() ended\")\n        return san_list\n\n    def _list_regex_check(self, entry: str, list_: List[str]) -> bool:\n        \"\"\"check entry against regex\"\"\"\n        self.logger.debug(\"EABhandler._list_regex_check()\")\n\n        check_result = False\n        for regex in list_:\n            if regex.startswith(\"*.\"):\n                regex = regex.replace(\"*.\", \".\")\n            regex_compiled = re.compile(regex)\n            if bool(regex_compiled.search(entry)):\n                # parameter is in set flag accordingly and stop loop\n                check_result = True\n\n        self.logger.debug(\n            \"EABhandler._list_regex_check() ended with: {0}\".format(check_result)\n        )\n        return check_result\n\n    def _wllist_check(self, entry: str, list_: List[str], toggle: bool = False) -> bool:\n        \"\"\"check string against list\"\"\"\n        self.logger.debug(\"EABhandler._wllist_check({0}:{1})\".format(entry, toggle))\n        self.logger.debug(\"check against list: {0}\".format(list_))\n\n        # default setting\n        check_result = False\n\n        if entry:\n            if list_:\n                check_result = self._list_regex_check(entry, list_)\n            else:\n                # empty list, flip parameter to make the check successful\n                check_result = True\n\n        if toggle:\n            # toggle result if this is a blocked_domainlist\n            check_result = not check_result\n\n        self.logger.debug(\n            \"EABhandler._wllist_check() ended with: {0}\".format(check_result)\n        )\n        return check_result\n\n    def _allowed_domains_check(self, csr: str, domain_list: List[str]) -> str:\n        \"\"\"check allowed domains\"\"\"\n        self.logger.debug(\"EABhandler.allowed_domains_check()\")\n\n        (san_list, check_list) = self._chk_san_lists_get(csr)\n        (san_list) = self._cn_add(csr, san_list)\n\n        # go over the san list and check each entry\n        for san in san_list:\n            check_list.append(self._wllist_check(san, domain_list))\n\n        if check_list:\n            # cover a cornercase with empty checklist (no san, no cn)\n            if False in check_list:\n                result = \"Either CN or SANs are not allowed by profile\"\n            else:\n                result = False\n\n        self.logger.debug(\"EABhandler.allowed_domains_check() ended with: %s\", result)\n        return result\n\n    def eab_kid_get(self, csr: str, revocation=False) -> str:\n        \"\"\"get eab kid  from datbases based on csr\"\"\"\n        self.logger.debug(\"EABhandler.eab_kid_get()\")\n        try:\n            # look up eab_kid from database based on csr\n            from acme_srv.db_handler import DBstore  # pylint: disable=c0415\n\n            if revocation:\n                # this is a lookup for a revocation request\n                search_key = \"cert_raw\"\n            else:\n                # this is a lookup for an enrollment request\n                search_key = \"csr\"\n\n            dbstore = DBstore(False, self.logger)\n            result_dic = dbstore.certificate_lookup(\n                search_key,\n                csr,\n                vlist=[\n                    \"name\",\n                    \"order__name\",\n                    \"order__account__name\",\n                    \"order__account__eab_kid\",\n                ],\n            )\n            if result_dic and \"order__account__eab_kid\" in result_dic:\n                eab_kid = result_dic[\"order__account__eab_kid\"]\n            else:\n                eab_kid = None\n        except Exception as err:\n            self.logger.error(\"Database error while retrieving eab_kid: %s\", err)\n            eab_kid = None\n\n        self.logger.debug(\"EABhandler.eab_kid_get() ended with: %s\", eab_kid)\n        return eab_kid\n\n    def eab_profile_get(self, csr: str, revocation=False) -> str:\n        \"\"\"get eab profile\"\"\"\n        self.logger.debug(\"EABhandler._eab_profile_get()\")\n\n        # load profiles from key_file\n        profiles_dic = self.key_file_load()\n\n        # get eab_kid from database\n        eab_kid = self.eab_kid_get(csr, revocation=revocation)\n\n        # get profile from profiles_dic\n        if (\n            profiles_dic\n            and eab_kid\n            and eab_kid in profiles_dic\n            and \"cahandler\" in profiles_dic[eab_kid]\n        ):\n            profile_dic = profiles_dic[eab_kid][\"cahandler\"]\n        else:\n            profile_dic = {}\n\n        self.logger.debug(\n            \"EABhandler._eab_profile_get() ended with: %s\", bool(profile_dic)\n        )\n        return profile_dic\n\n    def keyfile_content_load(self, key_file_content) -> dict:\n        \"\"\"load profiles from key_file\"\"\"\n        self.logger.debug(\"EABhandler.keyfile_content_load()\")\n\n        try:\n            profiles_dic = json.loads(key_file_content)\n        except Exception as err:\n            self.logger.error(\"Failed to parse key file content as JSON: %s\", err)\n            try:\n                profiles_dic = yaml.safe_load(key_file_content)\n            except Exception as err:\n                self.logger.error(\"Failed to parse key file content as YAML: %s\", err)\n                profiles_dic = {}\n\n        self.logger.debug(\n            \"EABhandler.keyfile_content_load() ended with %s\", bool(profiles_dic)\n        )\n        return profiles_dic\n\n    def key_file_load(self):\n        \"\"\"load profiles from key_file\"\"\"\n        self.logger.debug(\"EABhandler.key_file_load()\")\n\n        if self.key_file:\n            try:\n                with open(self.key_file, encoding=\"utf8\") as key_file_content:\n                    profiles_dic = self.keyfile_content_load(key_file_content.read())\n            except Exception as err:\n                self.logger.error(\"Failed to load key file: %s\", err)\n                profiles_dic = {}\n        else:\n            self.logger.error(\"No key_file specified for EAB profile loading.\")\n            profiles_dic = {}\n\n        self.logger.debug(\n            \"EABhandler.key_file_load() ended with %s\", bool(profiles_dic)\n        )\n        return profiles_dic\n\n    def mac_key_get(self, kid: str = None) -> str:\n        \"\"\"check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get({})\".format(kid))\n\n        mac_key = None\n        try:\n            if self.key_file and kid:\n                with open(self.key_file, encoding=\"utf8\") as key_file_content:\n                    data_dic = self.keyfile_content_load(key_file_content.read())\n                    if kid in data_dic and \"hmac\" in data_dic[kid]:\n                        mac_key = data_dic[kid][\"hmac\"]\n        except Exception as err:\n            self.logger.error(\"Failed to retrieve MAC key for kid '%s': %s\", kid, err)\n\n        self.logger.debug(\n            \"EABhandler.mac_key_get() ended with: {0}\".format(bool(mac_key))\n        )\n        return mac_key\n"
  },
  {
    "path": "examples/eab_handler/kid_profiles.json",
    "content": "{\n  \"keyid_00\": {\n    \"hmac\": \"V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\",\n    \"cahandler\": {\n      \"profile_id\": [\"profile_1\", \"profile_2\", \"profile_3\"],\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.example.net\"],\n      \"ca_name\": \"example_ca\",\n      \"api_user\": \"api_user\",\n      \"api_password\": \"api_password\"\n    }\n  },\n  \"keyid_01\": {\n    \"hmac\": \"YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\",\n    \"cahandler\": {\n      \"profile_id\": \"profile_2\",\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\", \"*.example.net\"],\n      \"ca_name\": \"example_ca_2\",\n      \"api_user\": \"api_user_2\",\n      \"api_password\": \"api_password_2\"\n    },\n    \"challenge\": {\n      \"challenge_validation_disable\": \"True\"\n    }\n  },\n  \"keyid_02\": {\n    \"hmac\": \"dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\",\n    \"cahandler\": {\n      \"allowed_domainlist\": [\"www.example.com\", \"www.example.org\"]\n    },\n    \"challenge\": {\n      \"challenge_validation_disable\": \"True\",\n      \"foward_address_check\": \"True\",\n      \"reverse_address_check\": \"True\"\n    }\n  },\n  \"keyid_03\": {\n    \"hmac\": \"YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\",\n    \"authorization\": {\n      \"prevalidated_domainlist\": [\"www.example.com\"]\n    }\n  }\n}\n"
  },
  {
    "path": "examples/eab_handler/kid_profiles.yml",
    "content": "---\nkeyid_00:\n  hmac: V2VfbmVlZF9hbm90aGVyX3ZlcnkfX2xvbmdfaG1hY190b19jaGVja19lYWJfZm9yX2tleWlkXzAwX2FzX2xlZ29fZW5mb3JjZXNfYW5faG1hY19sb25nZXJfdGhhbl8yNTZfYml0cw\n  cahandler:\n    profile_id:\n    - profile_1\n    - profile_2\n    - profile_3\n    allowed_domainlist:\n    - www.example.com\n    - www.example.org\n    - \"*.example.net\"\n    ca_name: example_ca\n    api_user: api_user\n    api_password: api_password\nkeyid_01:\n  hmac: YW5vdXRoZXJfdmVyeV9sb25nX2htYWNfZm9yX2tleWlkXzAxX3doaWNoIHdpbGxfYmUgdXNlZF9kdXJpbmcgcmVncmVzc2lvbg\n  cahandler:\n    profile_id: profile_2\n    allowed_domainlist:\n    - www.example.com\n    - www.example.org\n    - \"*.example.net\"\n    ca_name: example_ca_2\n    api_user: api_user_2\n    api_password: api_password_2\nkeyid_02:\n  hmac: dGhpc19pc19hX3ZlcnlfbG9uZ19obWFjX3RvX21ha2Vfc3VyZV90aGF0X2l0c19tb3JlX3RoYW5fMjU2X2JpdHM\n  cahandler:\n    allowed_domainlist:\n    - www.example.com\n    - www.example.org\nkeyid_03:\n  hmac: YW5kX2ZpbmFsbHlfdGhlX2xhc3RfaG1hY19rZXlfd2hpY2hfaXNfbG9uZ2VyX3RoYW5fMjU2X2JpdHNfYW5kX3Nob3VsZF93b3Jr\n"
  },
  {
    "path": "examples/eab_handler/skeleton_eab_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"eab json handler\"\"\"\nfrom __future__ import print_function\n\n# pylint: disable=E0401\nfrom acme_srv.helper import load_config\n\n\nclass EABhandler(object):\n    \"\"\"EAB file handler\"\"\"\n\n    def __init__(self, logger: object = None):\n        self.logger = logger\n        self.key = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        if not self.key:\n            self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"EABhandler\")\n        if \"EABhandler\" in config_dic and \"key\" in config_dic[\"EABhandler\"]:\n            self.key = config_dic[\"EABhandler\"][\"key\"]\n\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def allowed_domains_check(self, csr, value) -> str:\n        \"\"\"check allowed domains\"\"\"\n        self.logger.debug(\"EABhandler.allowed_domains_check(%s, %s)\", csr, value)\n        error = \"ERROR\"  # error message or None\n\n        return error\n\n    def mac_key_get(self, kid: str = None) -> str:\n        \"\"\"check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get(%s)\", kid)\n        mac_key = \"mac_key\"\n\n        return mac_key\n"
  },
  {
    "path": "examples/eab_handler/sql_handler.py",
    "content": "#!/usr/bin/python\n\n# -*- coding: utf-8 -*-\n\"\"\"eab sql handler\"\"\"\n\nfrom __future__ import print_function\n\nfrom logging import Logger\nimport psycopg2\nimport re\nfrom mssql_python import connect\nfrom typing import Dict, List, Optional, Tuple\n\nfrom acme_srv.helper import load_config, csr_cn_get, csr_san_get\n\n\nclass EABhandler(object):\n    \"\"\"EAB SQL handler\"\"\"\n\n    def __init__(self, logger: Logger):\n        self.logger = logger\n\n        self.db_system = None\n        self.db_host = None\n        self.db_name = None\n        self.db_user = None\n        self.db_password = None\n\n    def __enter__(self):\n        \"\"\"Makes EABhandler a Context Manager\"\"\"\n        self._config_load()\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"Close the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\"Load config from file\"\"\"\n        self.logger.debug(\"EABhandler._config_load()\")\n\n        config_dic = load_config(self.logger, \"EABhandler\")\n\n        self.db_system = config_dic.get(\n            \"EABhandler\", \"db_system\", fallback=self.db_system\n        )\n        self.db_host = config_dic.get(\"EABhandler\", \"db_host\", fallback=self.db_host)\n        self.db_name = config_dic.get(\"EABhandler\", \"db_name\", fallback=self.db_name)\n        self.db_user = config_dic.get(\"EABhandler\", \"db_user\", fallback=self.db_user)\n        self.db_password = config_dic.get(\n            \"EABhandler\", \"db_password\", fallback=self.db_password\n        )\n\n        self.logger.debug(\"EABhandler._config_load() ended\")\n\n    def _chk_san_lists_get(self, csr: str) -> Tuple[List[str], List[bool]]:\n        \"\"\"Check lists\"\"\"\n        self.logger.debug(\"EABhandler._chk_san_lists_get()\")\n\n        # get sans and build a list\n        _san_list = csr_san_get(self.logger, csr)\n\n        check_list = []\n        san_list = []\n\n        if _san_list:\n            for san in _san_list:\n                try:\n                    # SAN list must be modified/filtered)\n                    (_san_type, san_value) = san.lower().split(\":\")\n                    san_list.append(san_value)\n                except Exception:\n                    # force check to fail as something went wrong during parsing\n                    check_list.append(False)\n                    self.logger.info(\n                        \"SAN list parsing failed at entry: {0}\".format(san)\n                    )\n\n        self.logger.debug(\"EABhandler._chk_san_lists_get() ended\")\n        return (san_list, check_list)\n\n    def _cn_add(self, csr: str, san_list: List[str]) -> Tuple[List[str], str]:\n        \"\"\"Add CN if required\"\"\"\n        self.logger.debug(\"EABhandler._cn_add()\")\n\n        # get common name and attach it to san_list\n        cn_ = csr_cn_get(self.logger, csr)\n\n        if cn_:\n            cn_ = cn_.lower()\n            if cn_ not in san_list:\n                # append cn to san_list\n                self.logger.debug(\"EABhandler._csr_check(): append cn to san_list\")\n                san_list.append(cn_)\n\n        self.logger.debug(\"EABhandler._cn_add() ended\")\n        return san_list\n\n    def _list_regex_check(self, entry: str, list_: List[str]) -> bool:\n        \"\"\"Check entry against regex\"\"\"\n        self.logger.debug(\"EABhandler._list_regex_check()\")\n\n        check_result = False\n        for regex in list_:\n            if regex.startswith(\"*.\"):\n                regex = regex.replace(\"*.\", \".\")\n            regex_compiled = re.compile(regex)\n            if bool(regex_compiled.search(entry)):\n                # parameter is in set flag accordingly and stop loop\n                check_result = True\n\n        self.logger.debug(\n            \"EABhandler._list_regex_check() ended with: {0}\".format(check_result)\n        )\n        return check_result\n\n    def _wllist_check(self, entry: str, list_: List[str], toggle: bool = False) -> bool:\n        \"\"\"Check string against list\"\"\"\n        self.logger.debug(\"EABhandler._wllist_check({0}:{1})\".format(entry, toggle))\n        self.logger.debug(\"check against list: {0}\".format(list_))\n\n        # default setting\n        check_result = False\n\n        if entry:\n            if list_:\n                check_result = self._list_regex_check(entry, list_)\n            else:\n                # empty list, flip parameter to make the check successful\n                check_result = True\n\n        if toggle:\n            # toggle result if this is a blocked_domainlist\n            check_result = not check_result\n\n        self.logger.debug(\n            \"EABhandler._wllist_check() ended with: {0}\".format(check_result)\n        )\n        return check_result\n\n    def _allowed_domains_check(self, csr: str, domain_list: List[str]) -> str:\n        \"\"\"Check allowed domains\"\"\"\n        self.logger.debug(\"EABhandler.allowed_domains_check()\")\n\n        (san_list, check_list) = self._chk_san_lists_get(csr)\n        (san_list) = self._cn_add(csr, san_list)\n\n        # go over the san list and check each entry\n        for san in san_list:\n            check_list.append(self._wllist_check(san, domain_list))\n\n        if check_list:\n            # cover a cornercase with empty checklist (no san, no cn)\n            if False in check_list:\n                result = \"Either CN or SANs are not allowed by profile\"\n            else:\n                result = False\n\n        self.logger.debug(\"EABhandler.allowed_domains_check() ended with: %s\", result)\n        return result\n\n    def eab_kid_get(self, csr: str, revocation=False) -> str:\n        \"\"\"Get eab kid from database based on csr\"\"\"\n        self.logger.debug(\"EABhandler.eab_kid_get()\")\n\n        try:\n            # look up eab_kid from database based on csr\n            from acme_srv.db_handler import DBstore  # pylint: disable=c0415\n\n            if revocation:\n                # this is a lookup for a revocation request\n                search_key = \"cert_raw\"\n            else:\n                # this is a lookup for an enrollment request\n                search_key = \"csr\"\n\n            dbstore = DBstore(False, self.logger)\n            result_dic = dbstore.certificate_lookup(\n                search_key,\n                csr,\n                vlist=[\n                    \"name\",\n                    \"order__name\",\n                    \"order__account__name\",\n                    \"order__account__eab_kid\",\n                ],\n            )\n            if result_dic and \"order__account__eab_kid\" in result_dic:\n                eab_kid = result_dic[\"order__account__eab_kid\"]\n            else:\n                eab_kid = None\n\n        except Exception as err:\n            self.logger.error(\"Database error while retrieving eab_kid: %s\", err)\n            eab_kid = None\n\n        self.logger.debug(\"EABhandler.eab_kid_get() ended with: %s\", eab_kid)\n        return eab_kid\n\n    def eab_profile_get(self, csr: str, revocation=False) -> str:\n        \"\"\"Get eab profile\"\"\"\n        self.logger.debug(\"EABhandler._eab_profile_get()\")\n\n        # load profiles from eab credentials database\n        profiles_dic = self.key_file_load()\n\n        # get eab_kid from database\n        eab_kid = self.eab_kid_get(csr, revocation=revocation)\n\n        # get profile from profiles_dic\n        if (\n            profiles_dic\n            and eab_kid\n            and eab_kid in profiles_dic\n            and \"cahandler\" in profiles_dic[eab_kid]\n        ):\n            profile_dic = profiles_dic[eab_kid][\"cahandler\"]\n        else:\n            profile_dic = {}\n\n        self.logger.debug(\n            \"EABhandler._eab_profile_get() ended with: %s\", bool(profile_dic)\n        )\n        return profile_dic\n\n    def key_file_load(self) -> Dict[str, str]:\n        \"\"\"Load profiles from eab credentials database\"\"\"\n        self.logger.debug(\"EABhandler.key_file_load()\")\n\n        data_dic = {}\n\n        if self.db_host and self.db_name and self.db_user and self.db_password:\n            SQL_QUERY = \"SELECT key_id, profile FROM credentials WHERE STATUS = 1;\"\n            if self.db_system == \"mssql\":\n                data_dic = self._load_mssql_profiles(SQL_QUERY)\n            elif self.db_system == \"postgres\":\n                data_dic = self._load_postgres_profiles(SQL_QUERY)\n\n        self.logger.debug(\"EABhandler.key_file.load() ended: {%s}\", bool(data_dic))\n        return data_dic\n\n    def _load_mssql_profiles(self, sql_query: str) -> Dict[str, str]:\n        \"\"\"Helper to load profiles from MSSQL\"\"\"\n        data_dic = {}\n        try:\n            conn_str = (\n                \"Server=\"\n                + self.db_host\n                + \";Database=\"\n                + self.db_name\n                + \";Encrypt=yes;UID=\"\n                + self.db_user\n                + \";PWD=\"\n                + self.db_password\n                + \";TrustServerCertificate=yes\"\n            )\n            conn = connect(conn_str)\n            cursor = conn.cursor()\n            cursor.execute(sql_query)\n            rows = cursor.fetchall()\n            for row in rows:\n                data_dic[row.key_id] = row.profile\n            conn.close()\n        except Exception as err:\n            self.logger.error(\"EABhandler._load_mssql_profiles() error: %s\", err)\n        return data_dic\n\n    def _load_postgres_profiles(self, sql_query: str) -> Dict[str, str]:\n        \"\"\"Helper to load profiles from Postgres\"\"\"\n        data_dic = {}\n        try:\n            conn = psycopg2.connect(\n                host=self.db_host,\n                dbname=self.db_name,\n                user=self.db_user,\n                password=self.db_password,\n            )\n            cursor = conn.cursor()\n            cursor.execute(sql_query)\n            rows = cursor.fetchall()\n            for row in rows:\n                data_dic[str(row[0])] = str(row[1])\n            conn.close()\n        except Exception as err:\n            self.logger.error(\"EABhandler._load_postgres_profiles() error: %s\", err)\n        return data_dic\n\n    def mac_key_get(self, key_id: str) -> Optional[str]:\n        \"\"\"Check external account binding\"\"\"\n        self.logger.debug(\"EABhandler.mac_key_get(%s)\", key_id)\n\n        mac_key = None\n\n        try:\n            if (\n                key_id\n                and self.db_host\n                and self.db_name\n                and self.db_user\n                and self.db_password\n            ):\n                data_dic = self.key_file_load()\n\n                if key_id in data_dic:\n                    mac_key = data_dic[key_id]\n            else:\n                self.logger.error(\"EABhandler.mac_key_get() error: key_id not found\")\n\n        except Exception as err:\n            self.logger.error(\n                \"Failed to retrieve MAC key for key_id '%s': %s\", key_id, err\n            )\n\n        self.logger.debug(\"EABhandler.mac_key_get() ended with %s\", bool(mac_key))\n        return mac_key\n"
  },
  {
    "path": "examples/ejbca/certprofile_acmeca1-673448746.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<java version=\"11.0.23\" class=\"java.beans.XMLDecoder\">\n <object class=\"java.util.LinkedHashMap\">\n  <void method=\"put\">\n   <string>version</string>\n   <float>52.0</float>\n  </void>\n  <void method=\"put\">\n   <string>type</string>\n   <int>1</int>\n  </void>\n  <void method=\"put\">\n   <string>certversion</string>\n   <string>X509v3</string>\n  </void>\n  <void method=\"put\">\n   <string>encodedvalidity</string>\n   <string>2y</string>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatevalidityoffset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatevalidityoffset</string>\n   <string>-10m</string>\n  </void>\n  <void method=\"put\">\n   <string>useexpirationrestrictionforweekdays</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>expirationrestrictionforweekdaysbefore</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>expirationrestrictionweekdays</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>allowvalidityoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>description</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>allowextensionoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowdnoverride</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowdnoverridebyeei</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowbackdatedrevokation</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatestorage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>storecertificatedata</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>storesubjectaltname</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usebasicconstrants</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>basicconstraintscritical</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectkeyidentifier</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectkeyidentifiercritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useauthoritykeyidentifier</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>authoritykeyidentifiercritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectalternativename</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectalternativenamecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useissueralternativename</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>issueralternativenamecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecrldistributionpoint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultcrldistributionpoint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>crldistributionpointcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>crldistributionpointuri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usefreshestcrl</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecadefinedfreshestcrl</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>freshestcrluri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>crlissuer</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatepolicies</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatepoliciescritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatepolicies</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>availablekeyalgorithms</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>DSA</string>\n    </void>\n    <void method=\"add\">\n     <string>ECDSA</string>\n    </void>\n    <void method=\"add\">\n     <string>RSA</string>\n    </void>\n    <void method=\"add\">\n     <string>Ed25519</string>\n    </void>\n    <void method=\"add\">\n     <string>Ed448</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>availableeccurves</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>ANY_EC_CURVE</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>availablebitlengths</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>110</int>\n    </void>\n    <void method=\"add\">\n     <int>112</int>\n    </void>\n    <void method=\"add\">\n     <int>113</int>\n    </void>\n    <void method=\"add\">\n     <int>126</int>\n    </void>\n    <void method=\"add\">\n     <int>128</int>\n    </void>\n    <void method=\"add\">\n     <int>131</int>\n    </void>\n    <void method=\"add\">\n     <int>160</int>\n    </void>\n    <void method=\"add\">\n     <int>161</int>\n    </void>\n    <void method=\"add\">\n     <int>162</int>\n    </void>\n    <void method=\"add\">\n     <int>163</int>\n    </void>\n    <void method=\"add\">\n     <int>167</int>\n    </void>\n    <void method=\"add\">\n     <int>173</int>\n    </void>\n    <void method=\"add\">\n     <int>179</int>\n    </void>\n    <void method=\"add\">\n     <int>189</int>\n    </void>\n    <void method=\"add\">\n     <int>190</int>\n    </void>\n    <void method=\"add\">\n     <int>191</int>\n    </void>\n    <void method=\"add\">\n     <int>192</int>\n    </void>\n    <void method=\"add\">\n     <int>193</int>\n    </void>\n    <void method=\"add\">\n     <int>224</int>\n    </void>\n    <void method=\"add\">\n     <int>225</int>\n    </void>\n    <void method=\"add\">\n     <int>232</int>\n    </void>\n    <void method=\"add\">\n     <int>233</int>\n    </void>\n    <void method=\"add\">\n     <int>236</int>\n    </void>\n    <void method=\"add\">\n     <int>237</int>\n    </void>\n    <void method=\"add\">\n     <int>238</int>\n    </void>\n    <void method=\"add\">\n     <int>239</int>\n    </void>\n    <void method=\"add\">\n     <int>256</int>\n    </void>\n    <void method=\"add\">\n     <int>257</int>\n    </void>\n    <void method=\"add\">\n     <int>281</int>\n    </void>\n    <void method=\"add\">\n     <int>282</int>\n    </void>\n    <void method=\"add\">\n     <int>289</int>\n    </void>\n    <void method=\"add\">\n     <int>307</int>\n    </void>\n    <void method=\"add\">\n     <int>320</int>\n    </void>\n    <void method=\"add\">\n     <int>353</int>\n    </void>\n    <void method=\"add\">\n     <int>367</int>\n    </void>\n    <void method=\"add\">\n     <int>384</int>\n    </void>\n    <void method=\"add\">\n     <int>407</int>\n    </void>\n    <void method=\"add\">\n     <int>409</int>\n    </void>\n    <void method=\"add\">\n     <int>418</int>\n    </void>\n    <void method=\"add\">\n     <int>431</int>\n    </void>\n    <void method=\"add\">\n     <int>512</int>\n    </void>\n    <void method=\"add\">\n     <int>521</int>\n    </void>\n    <void method=\"add\">\n     <int>570</int>\n    </void>\n    <void method=\"add\">\n     <int>1024</int>\n    </void>\n    <void method=\"add\">\n     <int>1536</int>\n    </void>\n    <void method=\"add\">\n     <int>2048</int>\n    </void>\n    <void method=\"add\">\n     <int>3072</int>\n    </void>\n    <void method=\"add\">\n     <int>4096</int>\n    </void>\n    <void method=\"add\">\n     <int>6144</int>\n    </void>\n    <void method=\"add\">\n     <int>8192</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>minimumavailablebitlength</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>maximumavailablebitlength</string>\n   <int>8192</int>\n  </void>\n  <void method=\"put\">\n   <string>signaturealgorithm</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>usekeyusage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>keyusage</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>allowkeyusageoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>keyusagecritical</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useextendedkeyusage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>extendedkeyusage</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>1.3.6.1.5.5.7.3.1</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>extendedkeyusagecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedocumenttypelist</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>documenttypelistcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>documenttypelist</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>availablecas</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>-1</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>usedpublishers</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>useocspnocheck</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useldapdnorder</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecustomdnorder</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usemicrosofttemplate</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>microsofttemplate</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usemsobjectsidextension</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecardnumber</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecnpostfix</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>cnpostfix</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectdnsubset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectdnsubset</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectaltnamesubset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectaltnamesubset</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usepathlengthconstraint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>pathlengthconstraint</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatement</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usepkixqcsyntaxv2</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatementcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatementraname</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcsematicsid</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsiqccompliance</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsisignaturedevice</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsivaluelimit</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimit</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimitexp</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimitcurrency</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsiretentionperiod</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qcetsiretentionperiod</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>useqccountries</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qccountriestring</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqccustomstring</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qccustomstringoid</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>qccustomstringtext</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>qcetsipds</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>qcetsitype</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyincerts</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyinocsp</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyinpublisher</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectdirattributes</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usenameconstraints</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useauthorityinformationaccess</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>caissuers</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultcaissuer</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultocspservicelocator</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>ocspservicelocatoruri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>cvcaccessrights</string>\n   <int>3</int>\n  </void>\n  <void method=\"put\">\n   <string>usedcertificateextensions</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>approvals</string>\n   <object class=\"java.util.LinkedHashMap\">\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>ADDEDITENDENTITY</string>\n     </object>\n     <int>-1</int>\n    </void>\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>KEYRECOVER</string>\n     </object>\n     <int>-1</int>\n    </void>\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>REVOCATION</string>\n     </object>\n     <int>-1</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiodnotbefore</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiod</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiodnotafter</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>privkeyusageperiodstartoffset</string>\n   <long>0</long>\n  </void>\n  <void method=\"put\">\n   <string>privkeyusageperiodlength</string>\n   <long>63072000</long>\n  </void>\n  <void method=\"put\">\n   <string>usesingleactivecertificateconstraint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>overridableextensionoids</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>nonoverridableextensionoids</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>eabnamespaces</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>usecabforganizationidentifier</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecustomdnorderldap</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowexpiredvalidityenddate</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>numofreqapprovals</string>\n   <int>1</int>\n  </void>\n  <void method=\"put\">\n   <string>approvalsettings</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>approvalProfile</string>\n   <int>-1</int>\n  </void>\n  <void method=\"put\">\n   <string>usetruncatedsubjectkeyidentifier</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usevalidityassuredshortterm</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>validityassuredshorttermcritical</string>\n   <boolean>false</boolean>\n  </void>\n </object>\n</java>\n"
  },
  {
    "path": "examples/ejbca/certprofile_acmeca2-83252423.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<java version=\"11.0.23\" class=\"java.beans.XMLDecoder\">\n <object class=\"java.util.LinkedHashMap\">\n  <void method=\"put\">\n   <string>version</string>\n   <float>52.0</float>\n  </void>\n  <void method=\"put\">\n   <string>type</string>\n   <int>1</int>\n  </void>\n  <void method=\"put\">\n   <string>certversion</string>\n   <string>X509v3</string>\n  </void>\n  <void method=\"put\">\n   <string>encodedvalidity</string>\n   <string>2y</string>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatevalidityoffset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatevalidityoffset</string>\n   <string>-10m</string>\n  </void>\n  <void method=\"put\">\n   <string>useexpirationrestrictionforweekdays</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>expirationrestrictionforweekdaysbefore</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>expirationrestrictionweekdays</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>allowvalidityoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>description</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>allowextensionoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowdnoverride</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowdnoverridebyeei</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowbackdatedrevokation</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatestorage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>storecertificatedata</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>storesubjectaltname</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usebasicconstrants</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>basicconstraintscritical</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectkeyidentifier</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectkeyidentifiercritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useauthoritykeyidentifier</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>authoritykeyidentifiercritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectalternativename</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectalternativenamecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useissueralternativename</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>issueralternativenamecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecrldistributionpoint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultcrldistributionpoint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>crldistributionpointcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>crldistributionpointuri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usefreshestcrl</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecadefinedfreshestcrl</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>freshestcrluri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>crlissuer</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatepolicies</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatepoliciescritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>certificatepolicies</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>availablekeyalgorithms</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>DSA</string>\n    </void>\n    <void method=\"add\">\n     <string>ECDSA</string>\n    </void>\n    <void method=\"add\">\n     <string>RSA</string>\n    </void>\n    <void method=\"add\">\n     <string>Ed25519</string>\n    </void>\n    <void method=\"add\">\n     <string>Ed448</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>availableeccurves</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>ANY_EC_CURVE</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>availablebitlengths</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>110</int>\n    </void>\n    <void method=\"add\">\n     <int>112</int>\n    </void>\n    <void method=\"add\">\n     <int>113</int>\n    </void>\n    <void method=\"add\">\n     <int>126</int>\n    </void>\n    <void method=\"add\">\n     <int>128</int>\n    </void>\n    <void method=\"add\">\n     <int>131</int>\n    </void>\n    <void method=\"add\">\n     <int>160</int>\n    </void>\n    <void method=\"add\">\n     <int>161</int>\n    </void>\n    <void method=\"add\">\n     <int>162</int>\n    </void>\n    <void method=\"add\">\n     <int>163</int>\n    </void>\n    <void method=\"add\">\n     <int>167</int>\n    </void>\n    <void method=\"add\">\n     <int>173</int>\n    </void>\n    <void method=\"add\">\n     <int>179</int>\n    </void>\n    <void method=\"add\">\n     <int>189</int>\n    </void>\n    <void method=\"add\">\n     <int>190</int>\n    </void>\n    <void method=\"add\">\n     <int>191</int>\n    </void>\n    <void method=\"add\">\n     <int>192</int>\n    </void>\n    <void method=\"add\">\n     <int>193</int>\n    </void>\n    <void method=\"add\">\n     <int>224</int>\n    </void>\n    <void method=\"add\">\n     <int>225</int>\n    </void>\n    <void method=\"add\">\n     <int>232</int>\n    </void>\n    <void method=\"add\">\n     <int>233</int>\n    </void>\n    <void method=\"add\">\n     <int>236</int>\n    </void>\n    <void method=\"add\">\n     <int>237</int>\n    </void>\n    <void method=\"add\">\n     <int>238</int>\n    </void>\n    <void method=\"add\">\n     <int>239</int>\n    </void>\n    <void method=\"add\">\n     <int>256</int>\n    </void>\n    <void method=\"add\">\n     <int>257</int>\n    </void>\n    <void method=\"add\">\n     <int>281</int>\n    </void>\n    <void method=\"add\">\n     <int>282</int>\n    </void>\n    <void method=\"add\">\n     <int>289</int>\n    </void>\n    <void method=\"add\">\n     <int>307</int>\n    </void>\n    <void method=\"add\">\n     <int>320</int>\n    </void>\n    <void method=\"add\">\n     <int>353</int>\n    </void>\n    <void method=\"add\">\n     <int>367</int>\n    </void>\n    <void method=\"add\">\n     <int>384</int>\n    </void>\n    <void method=\"add\">\n     <int>407</int>\n    </void>\n    <void method=\"add\">\n     <int>409</int>\n    </void>\n    <void method=\"add\">\n     <int>418</int>\n    </void>\n    <void method=\"add\">\n     <int>431</int>\n    </void>\n    <void method=\"add\">\n     <int>512</int>\n    </void>\n    <void method=\"add\">\n     <int>521</int>\n    </void>\n    <void method=\"add\">\n     <int>570</int>\n    </void>\n    <void method=\"add\">\n     <int>1024</int>\n    </void>\n    <void method=\"add\">\n     <int>1536</int>\n    </void>\n    <void method=\"add\">\n     <int>2048</int>\n    </void>\n    <void method=\"add\">\n     <int>3072</int>\n    </void>\n    <void method=\"add\">\n     <int>4096</int>\n    </void>\n    <void method=\"add\">\n     <int>6144</int>\n    </void>\n    <void method=\"add\">\n     <int>8192</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>minimumavailablebitlength</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>maximumavailablebitlength</string>\n   <int>8192</int>\n  </void>\n  <void method=\"put\">\n   <string>signaturealgorithm</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>usekeyusage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>keyusage</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>true</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n    <void method=\"add\">\n     <boolean>false</boolean>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>allowkeyusageoverride</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>keyusagecritical</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useextendedkeyusage</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>extendedkeyusage</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <string>1.3.6.1.5.5.7.3.2</string>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>extendedkeyusagecritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedocumenttypelist</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>documenttypelistcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>documenttypelist</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>availablecas</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>-1</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>usedpublishers</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>useocspnocheck</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useldapdnorder</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecustomdnorder</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usemicrosofttemplate</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>microsofttemplate</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usemsobjectsidextension</string>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecardnumber</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecnpostfix</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>cnpostfix</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectdnsubset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectdnsubset</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectaltnamesubset</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>subjectaltnamesubset</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usepathlengthconstraint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>pathlengthconstraint</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatement</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usepkixqcsyntaxv2</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatementcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcstatementraname</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcsematicsid</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsiqccompliance</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsisignaturedevice</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsivaluelimit</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimit</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimitexp</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>qcetsivaluelimitcurrency</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqcetsiretentionperiod</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qcetsiretentionperiod</string>\n   <int>0</int>\n  </void>\n  <void method=\"put\">\n   <string>useqccountries</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qccountriestring</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>useqccustomstring</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>qccustomstringoid</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>qccustomstringtext</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>qcetsipds</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>qcetsitype</string>\n   <null/>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyincerts</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyinocsp</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecertificatetransparencyinpublisher</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usesubjectdirattributes</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usenameconstraints</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useauthorityinformationaccess</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>caissuers</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultcaissuer</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usedefaultocspservicelocator</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>ocspservicelocatoruri</string>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>cvcaccessrights</string>\n   <int>3</int>\n  </void>\n  <void method=\"put\">\n   <string>usedcertificateextensions</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>approvals</string>\n   <object class=\"java.util.LinkedHashMap\">\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>KEYRECOVER</string>\n     </object>\n     <int>-1</int>\n    </void>\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>ADDEDITENDENTITY</string>\n     </object>\n     <int>-1</int>\n    </void>\n    <void method=\"put\">\n     <object class=\"java.lang.Enum\" method=\"valueOf\">\n      <class>org.cesecore.certificates.ca.ApprovalRequestType</class>\n      <string>REVOCATION</string>\n     </object>\n     <int>-1</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiodnotbefore</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiod</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>useprivkeyusageperiodnotafter</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>privkeyusageperiodstartoffset</string>\n   <long>0</long>\n  </void>\n  <void method=\"put\">\n   <string>privkeyusageperiodlength</string>\n   <long>63072000</long>\n  </void>\n  <void method=\"put\">\n   <string>usesingleactivecertificateconstraint</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>overridableextensionoids</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>nonoverridableextensionoids</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>eabnamespaces</string>\n   <object class=\"java.util.LinkedHashSet\"/>\n  </void>\n  <void method=\"put\">\n   <string>usecabforganizationidentifier</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usecustomdnorderldap</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>allowexpiredvalidityenddate</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>numofreqapprovals</string>\n   <int>1</int>\n  </void>\n  <void method=\"put\">\n   <string>approvalsettings</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>approvalProfile</string>\n   <int>-1</int>\n  </void>\n  <void method=\"put\">\n   <string>usetruncatedsubjectkeyidentifier</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>usevalidityassuredshortterm</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>validityassuredshorttermcritical</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>keyusageforbidencyrptionusageforecc</string>\n   <boolean>false</boolean>\n  </void>\n </object>\n</java>\n"
  },
  {
    "path": "examples/ejbca/entityprofile_acmeca-1535885215.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<java version=\"11.0.23\" class=\"java.beans.XMLDecoder\">\n <object class=\"java.util.LinkedHashMap\">\n  <void method=\"put\">\n   <string>version</string>\n   <float>18.0</float>\n  </void>\n  <void method=\"put\">\n   <string>NUMBERARRAY</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>6</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>0</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n    <void method=\"add\">\n     <int>1</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>SUBJECTDNFIELDORDER</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>50000</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>SUBJECTALTNAMEFIELDORDER</string>\n   <object class=\"java.util.ArrayList\">\n    <void method=\"add\">\n     <int>180000</int>\n    </void>\n    <void method=\"add\">\n     <int>180001</int>\n    </void>\n    <void method=\"add\">\n     <int>180002</int>\n    </void>\n    <void method=\"add\">\n     <int>180003</int>\n    </void>\n    <void method=\"add\">\n     <int>180004</int>\n    </void>\n    <void method=\"add\">\n     <int>180005</int>\n    </void>\n   </object>\n  </void>\n  <void method=\"put\">\n   <string>SUBJECTDIRATTRFIELDORDER</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>SSH_FIELD_ORDER</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <string>PROFILETYPE</string>\n   <int>1</int>\n  </void>\n  <void method=\"put\">\n   <int>0</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000000</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000000</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000000</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000000</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000001</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000001</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000001</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000001</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>95</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000095</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000095</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000095</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000095</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>96</int>\n   <string>8</string>\n  </void>\n  <void method=\"put\">\n   <int>2000096</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000096</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000096</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000096</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>26</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000026</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000026</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000026</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000026</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>29</int>\n   <string>673448746</string>\n  </void>\n  <void method=\"put\">\n   <int>2000029</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000029</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000029</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000029</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>30</int>\n   <string>673448746;83252423</string>\n  </void>\n  <void method=\"put\">\n   <int>2000030</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000030</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000030</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000030</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>31</int>\n   <string>1</string>\n  </void>\n  <void method=\"put\">\n   <int>2000031</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000031</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000031</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000031</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>32</int>\n   <string>1;2;5;3;4</string>\n  </void>\n  <void method=\"put\">\n   <int>2000032</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000032</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000032</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000032</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>33</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000033</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000033</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000033</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000033</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>34</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000034</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000034</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000034</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000034</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>38</int>\n   <string>1</string>\n  </void>\n  <void method=\"put\">\n   <int>2000038</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000038</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000038</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000038</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>37</int>\n   <string>-1165158710</string>\n  </void>\n  <void method=\"put\">\n   <int>2000037</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000037</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000037</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000037</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>98</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000098</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000098</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000098</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000098</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>99</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000099</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000099</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000099</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000099</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>97</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000097</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000097</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000097</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000097</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>91</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000091</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000091</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000091</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000091</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>94</int>\n   <string>-1</string>\n  </void>\n  <void method=\"put\">\n   <int>2000094</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000094</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000094</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000094</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>93</int>\n   <string>-1</string>\n  </void>\n  <void method=\"put\">\n   <int>2000093</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000093</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000093</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000093</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>89</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000089</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000089</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000089</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000089</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>88</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000088</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000088</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000088</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000088</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>87</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <int>2000087</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000087</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000087</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000087</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>86</int>\n   <string>7</string>\n  </void>\n  <void method=\"put\">\n   <int>2000086</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000086</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000086</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000086</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000201</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000202</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000090</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>90</int>\n   <string>0</string>\n  </void>\n  <void method=\"put\">\n   <int>1000002</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>2</int>\n   <string>false</string>\n  </void>\n  <void method=\"put\">\n   <int>2000002</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>110</int>\n   <string></string>\n  </void>\n  <void method=\"put\">\n   <string>REVERSEFFIELDCHECKS</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>ALLOW_MERGEDN</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>ALLOW_MULTI_VALUE_RDNS</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000092</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>USEEXTENSIONDATA</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>PSD2QCSTATEMENT</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000035</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>PRINTINGUSE</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>USERNOTIFICATIONS</string>\n   <object class=\"java.util.ArrayList\"/>\n  </void>\n  <void method=\"put\">\n   <int>1000028</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>2000028</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>28</int>\n   <string>false</string>\n  </void>\n  <void method=\"put\">\n   <int>2000035</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>35</int>\n   <string>false</string>\n  </void>\n  <void method=\"put\">\n   <string>PRINTINGREQUIRED</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>PRINTINGDEFAULT</string>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000005</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000005</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000005</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000005</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>18</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000018</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000018</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000018</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000018</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>118</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000118</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000118</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000118</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000118</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>218</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000218</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000218</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000218</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000218</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>318</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000318</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000318</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000318</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000318</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>418</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000418</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000418</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000418</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000418</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>518</int>\n   <string>.+</string>\n  </void>\n  <void method=\"put\">\n   <int>2000518</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <int>1000518</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>3000518</int>\n   <boolean>true</boolean>\n  </void>\n  <void method=\"put\">\n   <int>5000518</int>\n   <boolean>false</boolean>\n  </void>\n  <void method=\"put\">\n   <string>REDACTPII</string>\n   <boolean>false</boolean>\n  </void>\n </object>\n</java>\n"
  },
  {
    "path": "examples/hooks/cn_dump_hooks.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=C0209, E0401, R0913, W0613\n\"\"\"hook class for testing\"\"\"\nimport json\nfrom acme_srv.helper import load_config, cert_san_get, csr_san_get\n\n\nclass Hooks:\n    \"\"\"this handler dumps csr/cn common-names into text files\"\"\"\n\n    def __init__(self, logger) -> None:\n        self.logger = logger\n        self.save_path = None\n        self._config_load()\n\n    def __enter__(self):\n        \"\"\"Makes hook handler context manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=R0912, R0915\n        self.logger.debug(\"_config_load()\")\n        config_dic = load_config(self.logger, \"Hooks\")\n        if \"Hooks\" in config_dic and \"save_path\" in config_dic[\"Hooks\"]:\n            self.save_path = config_dic[\"Hooks\"][\"save_path\"]\n\n    def _file_append(self, filename, content):\n        \"\"\"save content to file\"\"\"\n        self.logger.debug(\"Hooks._file_append({0})\".format(filename))\n        with open(filename, \"a\", encoding=\"utf-8\") as fso:\n            fso.write(content)\n        self.logger.debug(\"Hooks._file_append() ended\")\n\n    def pre_hook(self, _certificate_name, _order_name, csr):\n        \"\"\"run before obtaining any certificates\"\"\"\n        self.logger.debug(\"Hook.pre_hook()\")\n        san_list = csr_san_get(self.logger, csr)\n        self._file_append(\n            \"{0}/pre_hook.txt\".format(self.save_path), json.dumps(san_list) + \"\\n\"\n        )\n\n    def post_hook(self, _certificate_name, _order_name, csr, _error):\n        \"\"\"run after *attempting* to obtain/renew certificates\"\"\"\n        self.logger.debug(\"Hook.post_hook()\")\n        san_list = csr_san_get(self.logger, csr)\n        self._file_append(\n            \"{0}/post_hook.txt\".format(self.save_path), json.dumps(san_list) + \"\\n\"\n        )\n\n    def success_hook(\n        self,\n        _certificate_name,\n        _order_name,\n        _csr,\n        _certificate,\n        certificate_raw,\n        _poll_identifier,\n    ):\n        \"\"\"run after each successfully certificate enrollment/renewal\"\"\"\n        self.logger.debug(\"Hook.success_hook()\")\n        san_list = cert_san_get(self.logger, certificate_raw)\n        self._file_append(\n            \"{0}/success_hook.txt\".format(self.save_path), json.dumps(san_list) + \"\\n\"\n        )\n"
  },
  {
    "path": "examples/hooks/email_hooks.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=C0209, E0401, C0413, C0301\n# -*- coding: utf-8 -*-\n\"\"\"\nEmail hook class\n\nExample config:\n\n[Hooks]\nhooks_file: email_hooks.py\nappname: acme2certifier\nemail_address: acme2certifier@acme.example.com\nrcpt: admin@example.com\nreport_failures: True\nreport_successes: False\n\n# Optional advanced configuration:\nsmtp_server: localhost\nsmtp_port: 25\nsubject_prefix: [ACME]\nsmtp_timeout: 30\nusername: your_smtp_user\npassword: your_smtp_password\nsmtp_use_tls: True\nsmtp_use_starttls: False\n\nAlternative config (using DEFAULT section):\n\n[DEFAULT]\nappname: acme2certifier\nemail_address: acme2certifier@acme.example.com\nrcpt: admin@example.com\nsmtp_server: localhost\nsmtp_port: 25\n\n[Hooks]\nhooks_file: email_hooks.py\n# Other settings inherited from DEFAULT\n\nConfiguration options:\nConfiguration parameters can be placed in either the [Hooks] section or the [DEFAULT] section.\nParameters in the [Hooks] section take precedence over those in [DEFAULT].\n- hooks_file: Path to this hooks file (required)\n- appname: Application name for email headers (required)\n- sender: Email sender address (required)\n- rcpt: Primary recipient email address (required)\n- report_failures: Send emails for certificate failures (default: True)\n- report_successes: Send emails for certificate successes (default: True)\n- smtp_server: SMTP server hostname (default: localhost)\n- smtp_port: SMTP server port (default: 25)\n- subject_prefix: Prefix for email subjects (optional)\n- smtp_timeout: SMTP connection timeout in seconds (default: 30)\n- username: SMTP authentication username (optional, defaults to sender email if password is provided)\n- password: SMTP authentication password (optional)\n- smtp_use_tls: Use TLS/SSL encryption (default: False for port 25, True for 465/587)\n- smtp_use_starttls: Use STARTTLS encryption (default: False)\n\n\"\"\"\n\nimport smtplib\nimport sys\n\nsys.path.insert(0, \"...\")\nsys.path.insert(1, \"..\")\nsys.path.insert(2, \".\")\n\nfrom acme_srv.helper import (  # noqa: E402\n    load_config,\n    cert_san_get,\n    csr_san_get,\n    build_pem_file,\n)\n\nfrom email.mime.application import MIMEApplication  # noqa: E402\nfrom email.mime.multipart import MIMEMultipart  # noqa: E402\nfrom email.mime.text import MIMEText  # noqa: E402\nfrom email.utils import formatdate  # noqa: E402\n\nfrom cryptography import x509  # noqa: E402\nfrom cryptography.hazmat.primitives import serialization  # noqa: E402\nfrom cryptography.hazmat.primitives.serialization import pkcs12  # noqa: E402\n\n\nclass Hooks:\n    \"\"\"Hook class to send email notifications on certificate events\"\"\"\n\n    def __init__(self, logger) -> None:\n        \"\"\"Initialize the Hooks class with configuration and logger\"\"\"\n        self.logger = logger\n\n        self.config_dic = load_config(self.logger, \"Hooks\")\n\n        self.msg: list[str] = []\n        self.san = \"\"\n\n        # Enhanced configuration validation\n        self._validate_configuration()\n        self._validate_smtp_configuration()\n        self._load_configuration()\n\n    def _validate_configuration(self) -> None:\n        \"\"\"Validate configuration\"\"\"\n        self.logger.debug(\"Hooks._validate_configuration()\")\n        if not self.config_dic:\n            raise ValueError(\"Configuration dictionary is empty or None\")\n\n        if \"Hooks\" not in self.config_dic and \"DEFAULT\" not in self.config_dic:\n            raise ValueError(\"Missing 'Hooks' or 'DEFAULT' section in configuration.\")\n\n        # Mandatory keys with validation\n        required_keys = [\"appname\"]\n        missing = []\n        empty = []\n\n        for key in required_keys:\n            value = self._get_config_value(key)\n            if value is None:\n                missing.append(key)\n            elif not value.strip():\n                empty.append(key)\n\n        if missing:\n            raise ValueError(\n                f\"Missing required configuration key(s) in [Hooks] or [DEFAULT]: {', '.join(missing)}\"\n            )\n        if empty:\n            raise ValueError(f\"Empty required configuration key(s): {', '.join(empty)}\")\n\n        self.logger.debug(\"Hooks._validate_configuration() ended successfully\")\n\n    def _get_config_value(self, key: str, fallback=None):\n        \"\"\"Get configuration value from 'Hooks' section first, then 'DEFAULT' section\"\"\"\n        # First try 'Hooks' section\n        if \"Hooks\" in self.config_dic and key in self.config_dic[\"Hooks\"]:\n            return self.config_dic[\"Hooks\"][key]\n\n        # Then try 'DEFAULT' section\n        if \"DEFAULT\" in self.config_dic and key in self.config_dic[\"DEFAULT\"]:\n            return self.config_dic[\"DEFAULT\"][key]\n\n        # Return fallback if not found in either section\n        return fallback\n\n    def _get_config_int(self, key: str, fallback=None):\n        \"\"\"Get integer configuration value from 'Hooks' or 'DEFAULT' section\"\"\"\n        value = self._get_config_value(key, fallback)\n        if value is None:\n            return fallback\n        try:\n            return int(value)\n        except (TypeError, ValueError):\n            return fallback\n\n    def _get_config_boolean(self, key: str, fallback=None):\n        \"\"\"Get boolean configuration value from 'Hooks' or 'DEFAULT' section\"\"\"\n        value = self._get_config_value(key, fallback)\n        if value is None:\n            return fallback\n        if isinstance(value, bool):\n            return value\n        return str(value).strip().lower() in (\"true\", \"1\", \"yes\", \"on\")\n\n    def _validate_smtp_configuration(self) -> None:\n        \"\"\"Validate SMTP-specific configuration\"\"\"\n        self.logger.debug(\"Hooks._validate_smtp_configuration()\")\n\n        # Validate SMTP port\n        smtp_port = self._get_config_int(\"smtp_port\", 25)\n\n        # Validate SMTP timeout\n        smtp_timeout = self._get_config_int(\"smtp_timeout\", 0)\n        if not smtp_timeout:\n            smtp_timeout = self._get_config_int(\"connection_timeout\", 30)\n\n        if smtp_timeout <= 0 or smtp_timeout > 300:\n            self.logger.error(\n                f\"Invalid SMTP timeout: {smtp_timeout}. Must be between 1-300 seconds\"\n            )\n\n        # Validate authentication configuration\n        smtp_username = self._get_config_value(\"smtp_username\", None)\n        if not smtp_username:\n            smtp_username = self._get_config_value(\"username\", None)\n        smtp_password = self._get_config_value(\"smtp_password\", None)\n        if not smtp_password:  #\n            smtp_password = self._get_config_value(\"password\", None)\n\n        # Check if password is provided without username (we'll use sender as username)\n        if smtp_password and not smtp_username:\n            self.logger.debug(\n                \"Hooks._validate_smtp_configuration() - SMTP password provided without username - will use sender email as username\"\n            )\n        elif smtp_username and not smtp_password:\n            self.logger.error(\"SMTP username provided but password is missing\")\n\n        # Warn about common configuration issues\n        smtp_use_tls = self._get_config_boolean(\"smtp_use_tls\", False)\n        smtp_use_starttls = self._get_config_boolean(\"smtp_use_starttls\", False)\n\n        if smtp_use_tls and smtp_use_starttls:\n            self.logger.warning(\n                \"Both smtp_use_tls and smtp_use_starttls are enabled. \"\n                \"smtp_use_tls takes precedence.\"\n            )\n\n        # Port-specific recommendations\n        if smtp_port == 465 and not smtp_use_tls:\n            self.logger.info(\n                \"Port 465 typically requires TLS. Consider setting smtp_use_tls=True\"\n            )\n        elif smtp_port == 587 and not smtp_use_starttls and not smtp_use_tls:\n            self.logger.info(\n                \"Port 587 typically requires STARTTLS. Consider setting smtp_use_starttls=True\"\n            )\n\n        self.logger.debug(\"Hooks._validate_smtp_configuration() ended successfully\")\n\n    def _load_configuration(self) -> None:\n        \"\"\"Load and assign configuration values\"\"\"\n        self.logger.debug(\"Hooks._load_configuration()\")\n        self.appname = self._get_config_value(\"appname\").strip()\n        self.sender = self._get_config_value(\"sender\")\n        if not self.sender:\n            self.sender = self._get_config_value(\"email_address\", None)\n        self.rcpt = self._get_config_value(\"rcpt\")\n\n        # Optionals, that default to True\n        self.report_failures = self.config_dic.getboolean(\n            \"Hooks\", \"report_failures\", fallback=True\n        )\n        self.report_successes = self.config_dic.getboolean(\n            \"Hooks\", \"report_successes\", fallback=True\n        )\n\n        # Additional email configuration options\n        self.smtp_server = self._get_config_value(\"smtp_server\", \"localhost\")\n        self.smtp_port = self._get_config_int(\"smtp_port\", 25)\n        self.email_subject_prefix = self._get_config_value(\"subject_prefix\", \"\")\n        self.smtp_timeout = self._get_config_int(\"smtp_timeout\", 30)\n\n        # SMTP Authentication configuration\n        self.smtp_username = self._get_config_value(\"smtp_username\", None)\n        if not self.smtp_username:\n            self.smtp_username = self._get_config_value(\"username\", None)\n        self.smtp_password = self._get_config_value(\"smtp_password\", None)\n        if not self.smtp_password:\n            self.smtp_password = self._get_config_value(\"password\", None)\n\n        # Use sender email as username if no explicit username provided but password is set\n        if not self.smtp_username and self.smtp_password:\n            self.smtp_username = self.sender\n            self.logger.debug(\n                f\"Hooks._load_configuration() - Using sender email as SMTP username: {self.smtp_username}\"\n            )\n\n        # SMTP Security configuration\n        self.smtp_use_tls = self._get_config_boolean(\"smtp_use_tls\", True)\n        self.smtp_use_starttls = self._get_config_boolean(\"smtp_use_starttls\", False)\n\n        self._setup_email_envelope()\n        self.logger.debug(\"Hooks._load_configuration() ended\")\n\n    def _setup_email_envelope(self) -> None:\n        \"\"\"Setup email envelope with enhanced configuration\"\"\"\n        self.logger.debug(\"Hooks._setup_email_envelope()\")\n        self.envelope = MIMEMultipart()\n        self.envelope[\"From\"] = f\"{self.appname} <{self.sender}>\"\n        self.envelope[\"To\"] = self.rcpt\n        self.envelope[\"Date\"] = formatdate()\n\n        self.done = False\n        self.logger.debug(\"Hooks._setup_email_envelope() ended\")\n\n    def _done(self):\n        \"\"\"Send the email\"\"\"\n        self.logger.debug(\"Hooks._done()\")\n        if self.done:\n            self.logger.warning(\"_done() called multiple times - email already sent\")\n            return\n\n        self.done = True\n\n        try:\n            self.logger.debug(\n                f\"Hooks._done() - Attempting to send email notification via {self.smtp_server}:{self.smtp_port} (timeout: {self.smtp_timeout}s)\"\n            )\n            self.logger.debug(\n                f\"Hooks._done() - TLS settings - use_tls: {self.smtp_use_tls}, use_starttls: {self.smtp_use_starttls}\"\n            )\n            self.logger.debug(\n                f\"Hooks._done() - Authentication - username: {self.smtp_username}, password: {'***' if self.smtp_password else 'None'}\"\n            )\n\n            # Choose appropriate SMTP class based on TLS configuration\n            if self.smtp_use_tls:\n                # Use SMTP_SSL for implicit TLS (usually port 465)\n                self.logger.debug(\n                    \"Hooks._done() - Using SMTP_SSL for implicit TLS connection\"\n                )\n                smtp = smtplib.SMTP_SSL(\n                    self.smtp_server, self.smtp_port, timeout=self.smtp_timeout\n                )\n            else:\n                # Use regular SMTP (usually port 25 or 587)\n                self.logger.debug(\n                    \"Hooks._done() - Using SMTP for plain or STARTTLS connection\"\n                )\n                smtp = smtplib.SMTP(\n                    self.smtp_server, self.smtp_port, timeout=self.smtp_timeout\n                )\n\n            with smtp:\n                # Enable debug output for SMTP\n                smtp.set_debuglevel(1)\n\n                self.logger.debug(\"Hooks._done() - Sending HELO/EHLO\")\n                smtp.ehlo()  # Use EHLO instead of HELO for better compatibility\n\n                # Enable STARTTLS if configured (for port 587 typically)\n                if self.smtp_use_starttls and not self.smtp_use_tls:\n                    self.logger.debug(\"Hooks._done() - Enabling STARTTLS encryption\")\n                    smtp.starttls()\n                    smtp.ehlo()  # Re-identify after STARTTLS\n\n                # Authenticate if credentials are provided\n                if self.smtp_username and self.smtp_password:\n                    self.logger.debug(\n                        f\"Hooks._done() - Authenticating with username: {self.smtp_username}\"\n                    )\n                    smtp.login(self.smtp_username, self.smtp_password)\n                    self.logger.debug(\"Hooks._done() - SMTP authentication successful\")\n                else:\n                    self.logger.debug(\n                        \"Hooks._done() - No SMTP authentication configured\"\n                    )\n\n                # Prepare and send the email\n                self.envelope.attach(MIMEText(\"\\n\\n\".join(self.msg), \"plain\"))\n\n                # Log email details before sending\n                subject = self.envelope[\"Subject\"]\n                self.logger.debug(\n                    f\"Hooks._done() - Sending email - From: {self.sender}, To: {self.rcpt}, Subject: {subject}\"\n                )\n\n                smtp.sendmail(self.sender, self.rcpt, self.envelope.as_string())\n\n            self.logger.info(\n                f\"Email notification sent successfully to {self.rcpt} - Subject: {subject}\"\n            )\n\n        except Exception as e:\n            error_msg = (\n                f\"Failed to send email notification: {type(e).__name__} - {str(e)}\"\n            )\n            self.logger.error(f\"Email sending failed: {error_msg}\")\n            return\n\n        self.logger.debug(\"Hooks._done() ended\")\n\n    def _clean_san(self, sans):\n        \"\"\"Clean and extract SAN with improved error handling\"\"\"\n        self.logger.debug(f\"Hooks._clean_san() called with SANs: {sans}\")\n        if not sans:\n            self.logger.warning(\"Empty SAN list provided\")\n            return \"unknown\"\n\n        if not isinstance(sans, list):\n            self.logger.warning(f\"SAN is not a list, got type: {type(sans)}\")\n            return \"unknown\"\n\n        # Grab the first one, file names can't be too long anyway\n        san_entry = sans[0]\n\n        if not san_entry or \":\" not in san_entry:\n            self.logger.warning(f\"Invalid SAN format: {san_entry}\")\n            return \"unknown\"\n\n        # Format: DNS:a.example.com\n        cleaned = san_entry.split(\":\")[1].strip()\n        self.logger.debug(f\"Cleaned SAN: {san_entry} -> {cleaned}\")\n        result = cleaned\n\n        self.logger.debug(f\"Final cleaned SAN: {san_entry} -> {result}\")\n        return result\n\n    def _attach_csr(self, request_key, csr):\n        \"\"\"Attach CSR\"\"\"\n        self.logger.debug(f\"Attaching CSR for request_key: {request_key}\")\n        try:\n            # Attach CSR\n            fn = f\"{self.san}_{request_key}.csr\"\n            csr_pem = build_pem_file(self.logger, None, csr, 64, True)\n\n            if not csr_pem:\n                self.logger.error(\"Failed to build PEM file from CSR\")\n                return\n\n            part = MIMEApplication(csr_pem, Name=fn)\n            part[\"Content-Disposition\"] = f'attachment; filename=\"{fn}\"'\n            part[\"Content-Type\"] = \"application/x-pem-file\"\n            self.envelope.attach(part)\n\n            self.msg.append(\n                f\"To read {fn} using CMD on Windows:\\\\ncertutil -dump %USERPROFILE%\\\\Downloads\\\\{fn}\"\n            )\n            self.logger.debug(\n                f\"Successfully attached CSR file: {fn} ({len(csr_pem)} bytes)\"\n            )\n\n        except Exception as e:\n            error_msg = f\"Failed to attach CSR: {e}\"\n            self.logger.warning(f\"{error_msg} (continuing without attachment\")\n            self.msg.append(f\"CSR attachment failed: {type(e).__name__}\")\n\n        self.logger.debug(\"Hooks._attach_csr() ended\")\n\n    def _attach_cert(self, request_key, certificate):\n        \"\"\"Attach certificate with enhanced error handling\"\"\"\n        self.logger.debug(f\"Attaching certificate for request_key: {request_key}\")\n        try:\n            self.logger.debug(f\"Attaching certificate for request_key: {request_key}\")\n\n            # Add crt to email\n            cert_list = x509.load_pem_x509_certificates(certificate.encode(\"utf-8\"))\n\n            # EE cert is at the start of the list\n            cert = cert_list.pop(0)\n\n            fn = f\"{self.san}_{request_key}.pfx\"\n\n            pfx = pkcs12.serialize_key_and_certificates(\n                self.san.encode(\"utf-8\"),\n                None,  # We don't even have the key, and obviously no need for it\n                cert,\n                cert_list,\n                serialization.NoEncryption(),  # No keys included so no encryption needed\n            )\n            part = MIMEApplication(pfx, Name=fn)\n            part[\"Content-Disposition\"] = f'attachment; filename=\"{fn}\"'\n            part[\"Content-Type\"] = \"application/x-pkcs12\"\n            self.envelope.attach(part)\n\n            self.msg.append(\n                f\"To read {fn} using CMD on Windows:\\\\ncertutil -dump %USERPROFILE%\\\\Downloads\\\\{fn}\"\n            )\n            self.logger.debug(\n                f\"Successfully attached certificate file: {fn} ({len(pfx)} bytes)\"\n            )\n\n        except Exception as e:\n            error_msg = f\"Certificate attachment failed: {type(e).__name__} - {str(e)}\"\n            self.logger.warning(f\"{error_msg} (continuing without attachment)\")\n            self.msg.append(f\"Certificate attachment failed: {type(e).__name__}\")\n\n        self.logger.debug(\"Hooks._attach_cert() ended\")\n\n    def _format_subject(self, status: str, san: str) -> str:\n        \"\"\"Format email subject with optional prefix and standardized format\"\"\"\n        self.logger.debug(\"Hooks._format_subject()\")\n        base_subject = f\"{self.appname} {status}: {san}\"\n        if self.email_subject_prefix:\n            return f\"{self.email_subject_prefix} {base_subject}\"\n        self.logger.debug(f\"Final email subject: {base_subject}\")\n        return base_subject\n\n    def _format_message_header(self, status: str, san: str) -> str:\n        \"\"\"Format standardized message header with timestamp and details\"\"\"\n        self.logger.debug(\"Hooks._format_message_header()\")\n        from datetime import datetime\n\n        timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n\n        header_lines = [\n            f\"ACME Certificate {status.title()} Notification\",\n            f\"Timestamp: {timestamp}\",\n            f\"Application: {self.appname}\",\n            f\"Subject Alternative Name: {san}\",\n            \"-\" * 50,\n        ]\n        self.logger.debug(\"Hooks._format_message_header() ended\")\n        return \"\\n\".join(header_lines)\n\n    def pre_hook(self, _certificate_name, _order_name, _csr) -> None:\n        \"\"\"Hook called before certificate processing - currently no action needed\"\"\"\n        self.logger.debug(\"Hook.pre_hook() called - no action required\")\n\n    def post_hook(self, request_key, _order_name, csr, error) -> None:\n        \"\"\"run after *attempting* to obtain/renew certificates\"\"\"\n        self.logger.debug(\"Hook.post_hook() called\")\n\n        if not self.report_failures:\n            self.logger.debug(\n                \"Hook.post_hook() disabled because report_failures is False\"\n            )\n            return\n\n        try:\n            self.san = self._clean_san(csr_san_get(self.logger, csr))\n\n            self.envelope[\"Subject\"] = self._format_subject(\"failure\", self.san)\n\n            # Create formatted message with header and error details\n            message_header = self._format_message_header(\"failure\", self.san)\n            error_details = f\"Error Details:\\n{error}\\n\\nRequest Key: {request_key}\"\n\n            self.msg.append(message_header)\n            self.msg.append(error_details)\n\n            self._attach_csr(request_key, csr)\n            self._done()\n\n        except Exception as e:\n            error_msg = f\"Error in post_hook: {type(e).__name__} - {str(e)}\"\n            self.logger.error(f\"{error_msg}\")\n            return\n\n        self.logger.debug(\"Hooks.post_hook() ended\")\n\n    def success_hook(\n        self,\n        request_key,\n        _order_name,\n        csr,\n        certificate,\n        certificate_raw,\n        _poll_identifier,\n    ) -> None:\n        \"\"\"run after each successful certificate enrollment/renewal\"\"\"\n        self.logger.debug(\"Hook.success_hook() called\")\n\n        if not self.report_successes:\n            self.logger.debug(\n                \"Hook.success_hook() disabled because report_successes is False\"\n            )\n            return\n\n        try:\n            self.san = self._clean_san(cert_san_get(self.logger, certificate_raw))\n\n            self.envelope[\"Subject\"] = self._format_subject(\"success\", self.san)\n\n            # Create formatted message with header and success details\n            message_header = self._format_message_header(\"success\", self.san)\n            success_details = (\n                f\"Certificate issued successfully!\\n\\nRequest Key: {request_key}\"\n            )\n\n            # Add certificate details if available\n            if certificate:\n                try:\n                    cert_list = x509.load_pem_x509_certificates(\n                        certificate.encode(\"utf-8\")\n                    )\n                    if cert_list:\n                        self.logger.debug(\n                            \"Hook.success_hook(): Parsing certificate details for email\"\n                        )\n                        cert = cert_list[0]\n                        success_details += f\"\\nSerial Number: {cert.serial_number}\"\n                        try:\n                            success_details += (\n                                f\"\\nValid From: {cert.not_valid_before_utc}\"\n                            )\n                            success_details += (\n                                f\"\\nValid Until: {cert.not_valid_after_utc}\"\n                            )\n                        except Exception:\n                            # fallback to older cryptography versions\n                            self.logger.debug(\n                                \"Hook.success_hook(): Falling back to not_valid_before and not_valid_after for certificate dates\"\n                            )\n                            success_details += f\"\\nValid From: {cert.not_valid_before}\"\n                            success_details += f\"\\nValid Until: {cert.not_valid_after}\"\n                except Exception as e:\n                    self.logger.warning(f\"Failed to parse certificate details: {e}\")\n\n            self.msg.append(message_header)\n            self.msg.append(success_details)\n\n            self._attach_csr(request_key, csr)\n            self._attach_cert(request_key, certificate)\n\n            self._done()\n\n        except Exception as e:\n            error_msg = f\"Error in success_hook: {type(e).__name__} - {str(e)}\"\n            self.logger.error(f\"{error_msg})\")\n            return\n\n        self.logger.debug(\"Hooks.success_hook() ended\")\n\n\n# For local testing\nif __name__ == \"__main__\":  # pragma: no cover\n\n    import logging\n    from acme_srv.helper import generate_random_string\n\n    log_mode = logging.DEBUG\n    logging.basicConfig(level=log_mode)\n    LOGGER = logging.getLogger(__name__)\n\n    # Test CSR for something.example.com\n    CSR = (\n        \"MIIBTDCB0gIBADAgMR4wHAYDVQQDExVzb21ldGhpbmcuZXhhbXBsZS5jb20wdjAQBgcqhkjOPQIBBgUr\"\n        \"gQQAIgNiAATkHOWijzPd0n/exl3jPVrkPAAJeND6sHiOAecYxsQikE8ImBU1DT6RKBElLkUCF7ButTeq\"\n        \"xkkfRU4Kz3pfSZe75rVqXYfN7xUzXEt2+vpqpA65q8ZGrj9ZgXKxrA89E7agMzAxBgkqhkiG9w0BCQ4x\"\n        \"JDAiMCAGA1UdEQQZMBeCFXNvbWV0aGluZy5leGFtcGxlLmNvbTAKBggqhkjOPQQDAgNpADBmAjEAgirc\"\n        \"tuTr4+SRJh+MsmnScYkrV+yC1qRQwUMZGgQcPy4jxmemdyQ9p6y52dzk0j2sAjEA5+OyAcRqtWeLL1Xi\"\n        \"PoZH0NykBcCmQWMavJubfk0seZyFE0GsFjOPk7qAoJGVYZU8\"\n    )\n\n    # These are just mkcert certificates for something.example.com and an autogenerated CA\n    CERTIFICATE = (\n        \"-----BEGIN CERTIFICATE-----\\nMIIECjCCAnKgAwIBAgIRALGHaaUUFeRgIrUBibd8K3owDQYJKoZ\"\n        \"IhvcNAQELBQAw\\nVTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRUwEwYDVQQLDAxyb290\"\n        \"\\nQGJhc3Rpb24xHDAaBgNVBAMME21rY2VydCByb290QGJhc3Rpb24wHhcNMjUxMDAx\\nMTQyMjMxWhcN\"\n        \"MjgwMTAxMTQyMjMxWjBAMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxv\\ncG1lbnQgY2VydGlmaWNhdGUxFT\"\n        \"ATBgNVBAsMDHJvb3RAYmFzdGlvbjCCASIwDQYJ\\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVJJSx0\"\n        \"7B5xVBF7iT1jwvP9Q7sQHBAa\\nOSctCmm8FMgQAn0B1i/M5RORrxmsxe9TGYQN23mgZPrkhfFREbK3jF\"\n        \"1qDyi5aqyv\\nRUCY8c6V8gVNHqeFY/Fbo7eVpUmL6cEWCQa4/IyC8HZgWZPvK8DiNEKTS6fa++Wg\\ng7\"\n        \"hEl0Du9IENEdnJZ8S63UGUklNaUmn/lsD2SMgtDq0OJUYmU5Zn1Uryh8I4MJCu\\nHY/+i4CV+6tirKYN\"\n        \"eQYvX2lxY8AcYnRsg8x18IVO5fu7DoH18uK0YtlTMEYac+AX\\nOI/6B0C6NqXse71cQs53UF/O7ew+OC\"\n        \"kZ67CoYobAqeuiOVEEA+qTSUsCAwEAAaNq\\nMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG\"\n        \"AQUFBwMBMB8GA1UdIwQY\\nMBaAFEW2GtPZX80jY6cvOq8rMMAfW1hsMCAGA1UdEQQZMBeCFXNvbWV0aG\"\n        \"luZy5l\\neGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAkDCKBHuqVxcXgx7vhftzDE3M\\nj8x7WC\"\n        \"4di+rkIrxyJ3ulGHc7Pl2gyvMoKJxRCqcK4WgLH7AqDkRsQSF+/yvv+c0H\\nbUYjauPfDo1yUlLIQpo3\"\n        \"7uwJjsfQt4j/AFLpYHw2myqAsqMw1jwbXRuLyyiHWSay\\nljyHhWVnbZcLZNvBwL6bV0RCuRlWCFfjlA\"\n        \"6buXW3a23krjs8k5I4UhKaeX7d0Pvk\\nx/3JxjlGlOA8tYBT8+6Aq1xOIC1MuD8h/32Cxa7vDI9VyspY\"\n        \"bsbCBl5m2XD566/P\\nRE5rn62kBBHEXiIpFrE0R1d8MFTx9PEC00jVFDWnec3Ayl2TiTpptCF/Cb5S9K\"\n        \"6g\\nEdUFUkQj9dTxX8owUbm/tYGIYrwibWzTtscb75KjSzExnZApMfNgngke8r1f6P4Y\\nHRQQU7/0Bc\"\n        \"Di2GPzCy83rN3d2DFn6U66TZG0EEEdV1e1A0gsqfgx/b6YAZsZv26H\\nZ5IkqXdj3IZRDcwdYgaTrlsl\"\n        \"kPsantdPl+x/kxP5\\n-----END CERTIFICATE-----\\n\\n-----BEGIN CERTIFICATE-----\\nMIIE\"\n        \"ejCCAuKgAwIBAgIRAI5dQJ4OEZYF5sy28Iw+/zkwDQYJKoZIhvcNAQELBQAw\\nVTEeMBwGA1UEChMVbW\"\n        \"tjZXJ0IGRldmVsb3BtZW50IENBMRUwEwYDVQQLDAxyb290\\nQGJhc3Rpb24xHDAaBgNVBAMME21rY2Vy\"\n        \"dCByb290QGJhc3Rpb24wHhcNMjUxMDAx\\nMTQyMjMwWhcNMzUxMDAxMTQyMjMwWjBVMR4wHAYDVQQKEx\"\n        \"Vta2NlcnQgZGV2ZWxv\\ncG1lbnQgQ0ExFTATBgNVBAsMDHJvb3RAYmFzdGlvbjEcMBoGA1UEAwwTbWtj\"\n        \"ZXJ0\\nIHJvb3RAYmFzdGlvbjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKo5\\nMM8/lupi\"\n        \"8cOQqh5igXfGFrunERIiShzhV3EHVpQN+h3SU0BQF50DZHDTL1rHQqAn\\nhPK4fgZ37s9HjssysejgYK\"\n        \"61w9YgvoOd6dlsCTSYjpF19T9Dz5SY8yZz3lNLHcbg\\nN111PZP4hyN3BtNw4ttENGuKAqHgvFO/xmzM\"\n        \"gJtT62G4qq8VwHa8ktFa3b9Lh14/\\njEOjUIkgAgHE869/deebb2ENox7nL+W0VB9o0XCqMDYF0ZF6pw\"\n        \"4gVP2FgNbwjSgM\\nci/NCW99biGHOKA5LVG4d6nNxFgOg7GdEFExzzHjjyIYQBC/ZB7ulDyQQ6KcQRn5\"\n        \"\\nbvn83SuUZ1cGRSWSndosR3LhEJaxDLbr68X7byL7PNkBM4ILAGpd+oZLCM4Z9cpF\\njGW4GxilijEg\"\n        \"Smo7gLZk++oEh3O31Wt5dyGs2BHeUDf0rHG7z+agpzK0H6Ar9Rj5\\nurfDJvswioyU7jUxrpOg+4Wk/J\"\n        \"aJWncbU49fZRtAiwYZVVHyvKf5bn+bRJK+bQID\\nAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0T\"\n        \"AQH/BAgwBgEB/wIBADAdBgNV\\nHQ4EFgQURbYa09lfzSNjpy86ryswwB9bWGwwDQYJKoZIhvcNAQELBQ\"\n        \"ADggGBAF+y\\nWudDZVtWEbNpsSz5YvZ3W0BuNwaFo5TFYhzhh4ougs/SUhvPW5dAsVBJBjTgJ4fy\\nXm\"\n        \"miptcVzrvZiaB2+muL1PT/vUhFomuyqw46smzBIrUyHHmjqdoVIhmJ4XJq/eLS\\n7wMLDpTeH3kQaQWt\"\n        \"cK1EqlPOIMn5m/st663280lB2ICyv1zSQgWIkv4YpmzAuJcm\\nwYw899emEsSdf3q1lQoLR0NkBdRPSN\"\n        \"Zcnb9+wR98Iw5Rjca/7P0A1RbbEmbayXzf\\n4adhIZaaCBDhADcU6SBC5v8HsIj0tolyf7nTKarKJoKy\"\n        \"eY1i1sXrK28vZyWykLLD\\nQ7FHcRDfoAtJ2QUvxbpBXpDg/F79PDjrdjc6n8nn4RG+JIwO8j7t3GMB5c\"\n        \"MWOnKC\\nruQ4NuKcsWkcIaQIcxJTx+tOYyGqyAMzxA+VFTQ+HNjcFBnue/XJOya4dpOo1BEG\\nAacSqy\"\n        \"ipP2lMM8Xbje7snzwmutRdATxiyGKDzacEJWUMHzlkrX8WsFIUnVNMUA==\\n-----END CERTIFICATE\"\n        \"-----\"\n    )\n\n    # Generate the \"certificate_raw\" variable from the bundle in certificate since it has all the info no need to duplicate it\n    cert, ca = x509.load_pem_x509_certificates(CERTIFICATE.encode(\"utf-8\"))\n    CERTIFICATE_RAW = \"\".join(\n        cert.public_bytes(serialization.Encoding.PEM).decode(\"utf-8\").splitlines()[1:-1]\n    )\n\n    # Random request key\n    REQUEST_KEY = generate_random_string(LOGGER, 12)\n\n    # test a Failure message\n    h = Hooks(LOGGER)\n    h.post_hook(REQUEST_KEY, \"\", CSR, \"urn:ietf:params:acme:error:rejectedIdentifier\")\n\n    # test a Success message\n    h = Hooks(LOGGER)\n    h.success_hook(REQUEST_KEY, \"\", CSR, CERTIFICATE, CERTIFICATE_RAW, \"\")\n"
  },
  {
    "path": "examples/hooks/exception_test_hooks.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=e0401, r0913, w0613\n\"\"\"exception hook class for testing only\"\"\"\nfrom acme_srv.helper import load_config\n\n\nclass Hooks:\n    \"\"\"This handler does not do anything useful and is used to test proper exception handling during hook execution\"\"\"\n\n    def __init__(self, logger) -> None:\n        self.logger = logger\n        self.raise_pre_hook_exception = False\n        self.raise_success_hook_exception = False\n        self.raise_post_hook_exception = False\n        self._config_load()\n\n    def __enter__(self):\n        \"\"\"Makes hook handler context manager\"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"cose the connection at the end of the context\"\"\"\n\n    def _config_load(self):\n        \"\"\" \" load config from file\"\"\"\n        # pylint: disable=R0912, R0915\n        self.logger.debug(\"CAhandler._config_load()\")\n        config_dic = load_config(self.logger, \"Hooks\")\n        if \"Hooks\" in config_dic:\n            self.raise_pre_hook_exception = config_dic.getboolean(\n                \"Hooks\", \"raise_pre_hook_exception\", fallback=False\n            )\n            self.raise_success_hook_exception = config_dic.getboolean(\n                \"Hooks\", \"raise_success_hook_exception\", fallback=False\n            )\n            self.raise_post_hook_exception = config_dic.getboolean(\n                \"Hooks\", \"raise_post_hook_exception\", fallback=False\n            )\n\n    def pre_hook(self, certificate_name, order_name, _csr) -> None:\n        \"\"\"run before obtaining any certificates\"\"\"\n        self.logger.debug(\"Hook.pre_hook(%s/%s)\", certificate_name, order_name)\n        if self.raise_pre_hook_exception:\n            raise SystemError(\"raise_pre_hook_exception\")\n\n    def post_hook(self, certificate_name, order_name, _csr, _error) -> None:\n        \"\"\"run after *attempting* to obtain/renew certificates\"\"\"\n        self.logger.debug(\"Hook.post_hook(%s/%s)\", certificate_name, order_name)\n        if self.raise_post_hook_exception:\n            raise SystemError(\"raise_post_hook_exception\")\n\n    def success_hook(\n        self,\n        certificate_name,\n        order_name,\n        _csr,\n        _certificate,\n        _certificate_raw,\n        _poll_identifier,\n    ) -> None:\n        \"\"\"run after each successful certificate enrollment/renewal\"\"\"\n        self.logger.debug(\"Hook.success_hook(%s/%s)\", certificate_name, order_name)\n        if self.raise_success_hook_exception:\n            raise SystemError(\"raise_success_hook_exception\")\n"
  },
  {
    "path": "examples/hooks/skeleton_hooks.py",
    "content": "# -*- coding: utf-8 -*-\n# pylint: disable=r0913, w0613\n\"\"\"example hook class\"\"\"\n\n\nclass Hooks:\n    \"\"\"\n    This class provides three different methods:\n    - pre_hook (run before obtaining any certificates)\n    - post_hook (run after *attempting* to obtain/renew certificates; runs regardless of whether\n      obtain/renew succeeded or failed)\n    - success_hook (run after each successfully renewed certificate)\n\n    Each method should throw an Exception if an unrecoverable error occurs.\n\n    This class contains dummy implementations of these hooks. To actually use hooks, create a class\n    that contains all three methods; alternatively, you can create a subclass of this class and\n    overwrite one or multiple of the methods.\n    \"\"\"\n\n    def __init__(self, logger) -> None:\n        self.logger = logger\n\n    def pre_hook(self, certificate_name, order_name, csr) -> None:\n        \"\"\"run before obtaining any certificates\"\"\"\n        self.logger.debug(\"Hook.pre_hook()\")\n\n        _hook_list = [certificate_name, order_name, csr]\n\n    def post_hook(self, certificate_name, order_name, csr, error) -> None:\n        \"\"\"run after *attempting* to obtain/renew certificates\"\"\"\n        self.logger.debug(\"Hook.post_hook()\")\n\n        _hook_list = [certificate_name, order_name, csr, error]\n\n    def success_hook(\n        self,\n        certificate_name,\n        order_name,\n        csr,\n        certificate,\n        certificate_raw,\n        poll_identifier,\n    ) -> None:\n        \"\"\"run after each successful certificate enrollment/renewal\"\"\"\n        self.logger.debug(\"Hook.success_hook()\")\n\n        _hook_list = [\n            certificate_name,\n            order_name,\n            csr,\n            certificate,\n            certificate_raw,\n            poll_identifier,\n        ]\n"
  },
  {
    "path": "examples/install_scripts/a2c-centos9-nginx.sh",
    "content": "#!/bin/bash\n# acme2certifier script installing a2c on CentOS with NGINX as webserver\n# usage:\n#   - download acme2certifer and unpack it into a directory\n#   - enter the directory\n#   - execute this script with \"sh ./examples/install_scripts/a2c-centos9-nginx.sh\"\n\n# 1. install neded packages\necho \"## Installing missing packages\"\nyum install -y epel-release\nyum update -y\n\nyum install -y python-pip nginx python3-uwsgidecorators.x86_64 tar uwsgi-plugin-python3 policycoreutils-python-utils krb5-workstation krb5-libs krb5-devel gcc python3-devel procps syslog-ng\n\n# 2. create directory\nmkdir /opt/acme2certifier\n\necho \"## Download software from github\"\n# 3. download archive\ncd /tmp\n# curl https://codeload.github.com/grindsa/acme2certifier/tar.gz/refs/heads/master -o a2c-master.tgz\n# tar xvfz a2c-master.tgz\ncd /tmp/acme2certifier\n\n# 4 install modules\necho \"## Install missing python modules\"\npip install -r requirements.txt\n\n# copy data\necho \"## Copy needed data to /opt/acme2certifier\"\ncp -R ./* /opt/acme2certifier/\n\n# 5 copy acme-srv.cfg\ncp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /opt/acme2certifier/acme_srv/acme_srv.cfg\n\n# 9 copy db handler\ncp /opt/acme2certifier/examples/db_handler/wsgi_handler.py /opt/acme2certifier/acme_srv/db_handler.py\n\n# 10 copy wsgi file\ncp /opt/acme2certifier/examples/acme2certifier_wsgi.py /opt/acme2certifier/\n\n# 16 add uswgi plugin\necho \"## Modify acme2certifier.ini for Redhat/Centos and deviations\"\necho \"plugins = python3\" >> examples/nginx/acme2certifier.ini\ncp examples/nginx/acme2certifier.ini /opt/acme2certifier\n\n# 11-12 fix ownwership and permissions\necho \"## Set correct ownership\"\nchmod a+x /opt/acme2certifier/acme_srv\nchown -R nginx /opt/acme2certifier/acme_srv\n\n# 15 - 18 configure and enable uWSGI\necho \"## Configure and enable uWSGI services\"\ncp examples/nginx/uwsgi.service /etc/systemd/system/\nsystemctl enable uwsgi.service\nsystemctl start uwsgi\n\n# 19 - 20 configure nginxinsta\necho \"## Configure and enable nginx services\"\ncp examples/nginx/nginx_acme_srv.conf /etc/nginx/conf.d/nginx_acme_srv.conf\ncp examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/conf.d/nginx_acme_srv_ssl.conf\necho \"## Add keyfile and certificate\"\nmkdir -p /var/www/acme2certifier/volume/\ncp .github/acme2certifier_cert.pem /var/www/acme2certifier/volume/\ncp .github/acme2certifier_key.pem /var/www/acme2certifier/volume/\n\nsystemctl enable nginx.service\nsystemctl restart nginx\nsystemctl status nginx.service\n\necho \"## Add missing SELinux rules\"\n\ncat <<EOT > acme2certifier.te\nmodule acme2certifier 1.0;\n\nrequire {\n\ttype var_run_t;\n\ttype initrc_t;\n\ttype httpd_t;\n\tclass sock_file write;\n\tclass unix_stream_socket connectto;\n}\n\n#============= httpd_t ==============\nallow httpd_t initrc_t:unix_stream_socket connectto;\nallow httpd_t var_run_t:sock_file write;\nEOT\n\ncheckmodule -M -m -o acme2certifier.mod acme2certifier.te\nsemodule_package -o acme2certifier.pp -m acme2certifier.mod\nsemodule -i acme2certifier.pp\nexit 0\n"
  },
  {
    "path": "examples/install_scripts/a2c-ubuntu22-apache2.sh",
    "content": "#!/bin/bash\n# acme2certifier script installing a2c on CentOS with NGINX as webserver\n# usage:\n#   - download acme2certifer and unpack it into a directory\n#   - enter the directory\n#   - execute this script with \"sh ./examples/install_scripts/a2c-ubuntu22-apache2.sh\"\n\n# 1 install needed packages\nsudo apt-get update\nsudo apt-get install -y apache2 libapache2-mod-wsgi-py3 python3-pip apache2-data curl krb5-user libgssapi-krb5-2 libkrb5-3 python3-gssapi python3-jwcrypto\n\n# 2 check if mod wsgi got activated\napache2ctl -M | grep -i wsgi\n\n# 4 install needed python modules\nsudo pip3 install -r requirements.txt\nsudo pip3 install pyopenssl --upgrade\n\n# 5 configure apache2\nsudo cp examples/apache2/apache_wsgi.conf /etc/apache2/sites-available/acme2certifier.conf\n\n# 6 enable ssl\nsudo cp examples/apache2/apache_wsgi_ssl.conf /etc/apache2/sites-available/acme2certifier_ssl.conf\nsudo mkdir -p /var/www/acme2certifier/volume/\nsudo cp .github/acme2certifier.pem /var/www/acme2certifier/volume/\nsudo a2enmod ssl\n\n# 7 activate a2c\nsudo a2ensite acme2certifier.conf\nsudo a2ensite acme2certifier_ssl.conf\n\n# 8 create data directory\nsudo mkdir -p /var/www/acme2certifier\n\n# 9 copy main wsgi file\nsudo cp examples/acme2certifier_wsgi.py /var/www/acme2certifier\n\n# 10 copy components needed by a2c\nsudo mkdir /var/www/acme2certifier/examples\nsudo cp -R examples/ca_handler/ /var/www/acme2certifier/examples/ca_handler\nsudo cp -R examples/eab_handler/ /var/www/acme2certifier/examples/eab_handler\nsudo cp -R examples/hooks/ /var/www/acme2certifier/examples/hooks\nsudo cp -R examples/acme_srv.cfg /var/www/acme2certifier/examples/\nsudo cp -R tools/ /var/www/acme2certifier/tools\n\n# 11 create directory for server files\nsudo mkdir /var/www/acme2certifier/acme_srv\n\n# 12 copy files\nsudo cp -R acme_srv /var/www/acme2certifier/\n\n# 13 use default configuration file\nsudo cp examples/acme_srv.cfg /var/www/acme2certifier/acme_srv/\n\n# 14 configure a2c with openssl handler - to be modified!!!!\nsudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\nsudo mkdir -p /var/www/acme2certifier/volume/acme_ca/certs\nsudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /var/www/acme2certifier/volume/acme_ca/\n\n# 17 copy database handler\nsudo cp examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n\n# 18 set correct ownership\nsudo chown -R www-data.www-data /var/www/acme2certifier/\n\n# 19 set permssions\nsudo chmod a+x /var/www/acme2certifier/acme_srv\n\n# 20 delete default apache configuration and restart apache2 server\nsudo rm /etc/apache2/sites-enabled/000-default.conf\nsudo systemctl start apache2\n"
  },
  {
    "path": "examples/install_scripts/a2c-ubuntu22-nginx.sh",
    "content": "#!/bin/bash\n# acme2certifier script installing a2c on CentOS with NGINX as webserver\n# usage:\n#   - download acme2certifer and unpack it into a directory\n#   - enter the directory\n#   - execute this script with \"sh ./examples/install_scripts/a2c-ubuntu22-apache2.sh\"\n\n# 1 install needed packages\necho \"## Install missing packages\"\nsudo apt-get update\nsudo apt-get install -y python3-pip nginx uwsgi uwsgi-plugin-python3 curl krb5-user libgssapi-krb5-2 libkrb5-3 python3-gssapi python3-jwcrypto\n\n# 3 install needed python modules\necho \"## Install missing pythom modules\"\nsudo pip3 install -r requirements.txt\nsudo pip3 install pyopenssl --upgrade\n\n# 8 create data directory\necho \"## Create directory structure required by acme2certifier\"\nsudo mkdir -p /var/www/acme2certifier/examples\n\nsudo cp examples/acme2certifier_wsgi.py /var/www/acme2certifier/acme2certifier_wsgi.py\nsudo cp -R examples/ca_handler/ /var/www/acme2certifier/examples/ca_handler\nsudo cp -R examples/eab_handler/ /var/www/acme2certifier/examples/eab_handler\nsudo cp -R examples/hooks/ /var/www/acme2certifier/examples/hooks\nsudo cp -R examples/nginx/ /var/www/acme2certifier/examples/nginx\nsudo cp examples/acme_srv.cfg /var/www/acme2certifier/examples/\nsudo cp -R acme_srv/ /var/www/acme2certifier/acme_srv\nsudo cp -R tools/ /var/www/acme2certifier/tools\nsudo cp examples/db_handler/wsgi_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n\necho \"## Modify nginx configuration file\"\nsed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv.conf\nsed -i \"s/run\\/uwsgi\\/acme.sock/var\\/www\\/acme2certifier\\/acme.sock/g\" examples/nginx/nginx_acme_srv_ssl.conf\nsudo cp examples/nginx/nginx_acme_srv.conf /etc/nginx/sites-available/acme_srv.conf\nsudo cp examples/nginx/nginx_acme_srv_ssl.conf /etc/nginx/sites-available/acme_srv_ssl.conf\nsudo  rm /etc/nginx/sites-enabled/default\nsudo ln -s /etc/nginx/sites-available/acme_srv.conf /etc/nginx/sites-enabled/acme_srv.conf\nsudo ln -s /etc/nginx/sites-available/acme_srv_ssl.conf /etc/nginx/sites-enabled/acme_srv_ssl.conf\n\necho \"## Add keyfile and certificate\"\nsudo mkdir -p /var/www/acme2certifier/volume/\nsudo cp .github/acme2certifier_cert.pem /var/www/acme2certifier/volume/\nsudo cp .github/acme2certifier_key.pem /var/www/acme2certifier/volume/\n\necho \"## Modify uwsgi configuration file\"\nsed -i \"s/\\/run\\/uwsgi\\/acme.sock/acme.sock/g\" examples/nginx/acme2certifier.ini\nsed -i \"s/nginx/www-data/g\" examples/nginx/acme2certifier.ini\necho \"plugins=python3\" >> examples/nginx/acme2certifier.ini\nsudo cp examples/nginx/acme2certifier.ini /var/www/acme2certifier\n\n# 14 configure a2c with openssl handler - to be modified!!!!\necho \"## Configure openssl ca handler\"\nsudo cp .github/openssl_ca_handler.py_acme_srv_choosen_handler.cfg /var/www/acme2certifier/acme_srv/acme_srv.cfg\nsudo mkdir -p /var/www/acme2certifier/volume/acme_ca/certs\nsudo cp test/ca/sub-ca-key.pem test/ca/sub-ca-crl.pem test/ca/sub-ca-cert.pem test/ca/root-ca-cert.pem /var/www/acme2certifier/volume/acme_ca/\n\n# 18 set correct ownership\necho \"## Set ownership and permissions\"\nsudo chown -R www-data.www-data /var/www/acme2certifier/\n# 19 set permssions\nsudo chmod a+x /var/www/acme2certifier/acme_srv\n\necho \"## Create acme2certifier service\"\n\ncat <<EOT > acme2certifier.service\n[Unit]\nDescription=uWSGI instance to serve acme2certifier\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=/var/www/acme2certifier\nEnvironment=\"PATH=/var/www/acme2certifier\"\nExecStart=uwsgi --ini acme2certifier.ini\n\n[Install]\nWantedBy=multi-user.target\nEOT\n\nsudo cp  acme2certifier.service /etc/systemd/system/acme2certifier.service\n\necho \"## Restart acme2certifier\"\nsudo systemctl start acme2certifier\nsudo systemctl enable acme2certifier\n\necho \"## Restart nginx\"\nsudo systemctl restart nginx\n"
  },
  {
    "path": "examples/install_scripts/debian/acme2certifier.install",
    "content": "examples/acme2certifier_wsgi.py /var/www/acme2certifier/\nacme_srv /var/www/acme2certifier/\nexamples/acme_srv.cfg /var/www/acme2certifier/acme_srv/\nexamples/db_handler /var/www/acme2certifier/examples\nexamples/ca_handler /var/www/acme2certifier/examples\nexamples/eab_handler /var/www/acme2certifier/examples\nexamples/hooks /var/www/acme2certifier/examples\nexamples/trigger /var/www/acme2certifier/examples\nexamples/django /var/www/acme2certifier/examples\nexamples/nginx /var/www/acme2certifier/examples\nexamples/apache2 /var/www/acme2certifier/examples\ntools /var/www/acme2certifier/\nexamples/db_handler/wsgi*.* /var/www/acme2certifier/acme_srv/\ndocs /usr/share/doc/acme2certifier/\n"
  },
  {
    "path": "examples/install_scripts/debian/changelog",
    "content": "acme2certifier (__version__-1) stable; urgency=medium\n\n  * Initial release\n\n -- GrindSa <grindelsack@gmail.com>  Fri, 16 Dec 2022 18:41:04 +0000\n"
  },
  {
    "path": "examples/install_scripts/debian/conffiles",
    "content": "/var/www/acme2certifier/acme_srv/acme_srv.cfg\n"
  },
  {
    "path": "examples/install_scripts/debian/control",
    "content": "Source: acme2certifier\nSection: Network\nPriority: optional\nMaintainer: GrindSa <grindelsack@gmail.com>\nBuild-Depends: debhelper-compat (= 13)\nStandards-Version: 4.6.0\nHomepage: https://github.com/grindsa/acme2certifier\n#Vcs-Browser: https://salsa.debian.org/debian/acme2certifier\n#Vcs-Git: https://salsa.debian.org/debian/acme2certifier.git\nRules-Requires-Root: no\n\nPackage: acme2certifier\nArchitecture: all\nDepends: ${misc:Depends}, tzdata, python3-setuptools, python3-jwcrypto, python3-cryptography, python3-openssl, python3-dnspython, python3-pytzdata, python3-configargparse, python3-dateutil, python3-requests, python3-requests-ntlm, python3-socks, python3-josepy, python3-acme, python3-xmltodict, python3-pyasn1, python3-pyasn1-modules, python3-django, python3-mysqldb, python3-pymysql, python3-psycopg2, python3-yaml\nRecommends: python3-requests-gssapi\nDescription: Library implementing ACME server functionality\n acme2certifier is development project to create an ACME protocol proxy. Main intention is to provide ACME services on CA servers which do not support this protocol yet.\n After installation remember to install either NGINX or apache2!\n"
  },
  {
    "path": "examples/install_scripts/debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: acme2certifier\nUpstream-Contact: GrindSa <grindelsack@gmail.com>\nSource: https://github.com/grindsa/acme2certifier\n\nFiles: *\nCopyright: 2022 GrindSa <grindelsack@gmail.com>\n\nLicense: GPL-3\n\n This package is free software; you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation; either version 3 of the License, or\n (at your option) any later version.\n .\n This package is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU General Public License for more details.\n .\n You should have received a copy of the GNU General Public License\n along with this program. If not, see <https://www.gnu.org/licenses/>\n .\n On Debian systems, the complete text of the GNU General\n Public License version 2 can be found in \"/usr/share/common-licenses/GPL-2\".\n"
  },
  {
    "path": "examples/install_scripts/debian/postinst",
    "content": "#! /bin/sh\n# postinst script for erddcd\n#\n# see: dh_installdeb(1)\n\nset -e\n\n# summary of how this script can be called:\n#        * <postinst> 'configure' <most-recently-configured-version>\n#        * <old-postinst> 'abort-upgrade' <new version>\n#        * <conflictor's-postinst> 'abort-remove' 'in-favour' <package>\n#          <new-version>\n#        * <deconfigured's-postinst> 'abort-deconfigure' 'in-favour'\n#          <failed-install-package> <version> 'removing'\n#          <conflicting-package> <version>\n# for details, see /usr/share/doc/packaging-manual/\n#\n# quoting from the policy:\n#     Any necessary prompting should almost always be confined to the\n#     post-installation script, and should be protected with a conditional\n#     so that unnecessary prompting doesn't happen if a package's\n#     installation fails and the 'postinst' is called with 'abort-upgrade',\n#     'abort-remove' or 'abort-deconfigure'.\n\ncase \"$1\" in\n    configure)\n\n    if [ -d /var/www/acme2certifier/acme2certifier ]; then\n        echo \"django environment detected\"\n        cp -R /var/www/acme2certifier/examples/django/acme_srv/* /var/www/acme2certifier/acme_srv/\n        cp -f /var/www/acme2certifier/examples/db_handler/django_handler.py /var/www/acme2certifier/acme_srv/db_handler.py\n    fi\n\n\tchown -R www-data.www-data /var/www/acme2certifier\n    ;;\n\n    abort-upgrade|abort-remove|abort-deconfigure)\n\n    ;;\n\n    *)\n        echo \"postinst called with unknown argument \\`$1'\" >&2\n        exit 0\n    ;;\nesac\n\n# dh_installdeb will replace this with shell code automatically\n# generated by other debhelper scripts.\n\n#DEBHELPER#\n\nexit 0\n"
  },
  {
    "path": "examples/install_scripts/debian/rules",
    "content": "#!/usr/bin/make -f\n# See debhelper(7) (uncomment to enable)\n# output every command that modifies files on the build system.\nexport DH_VERBOSE = 1\n\n\n# see FEATURE AREAS in dpkg-buildflags(1)\n#export DEB_BUILD_MAINT_OPTIONS = hardening=+all\n\n# see ENVIRONMENT in dpkg-buildflags(1)\n# package maintainers to append CFLAGS\n#export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic\n# package maintainers to append LDFLAGS\n#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed\n\n%:\n\tdh $@\n\nbuild:\n\tcp examples/db_handler/wsgi_handler.py acme_srv/db_handler.py\n# dh_make generated override targets\n# This is example for Cmake (See https://bugs.debian.org/641051 )\n#override_dh_auto_configure:\n#\tdh_auto_configure -- \\\n#\t-DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)\n"
  },
  {
    "path": "examples/install_scripts/rpm/acme2certifier.spec",
    "content": "\n# Disable automatic requires/provides processing\nAutoReqProv: no\n\n%global         projname        acme2certifier\n%global         __python        %{__python3}\n%global         dest_dir        /opt\n%{!?_unitdir: %global _unitdir /usr/lib/systemd/system}\n\nSummary:        library implementing ACME server functionality\nName:           acme2certifier\n\n%define         ghowner   \t\tgrindsa\n\nVersion:        __version__\nRelease:        1.0\nLicense:        GPL3; @grindsa@github\nURL:            https://github.com/grindsa/acme2certifier\nRequires:       nginx\n# EPEL repo required\nRequires:       policycoreutils-python-utils\nRequires:       uwsgi-plugin-python3\nRequires:       python3-uwsgidecorators\nRequires:       tar\nRequires:       python3-dateutil\nRequires:       python3-pytz\nRequires:       python3-setuptools\nRequires:       python3-jwcrypto\nRequires:       python3-cryptography\nRequires:       python3-pyOpenSSL\nRequires:       python3-dns\nRequires:       python3-configargparse\nRequires:       python3-dateutil\nRequires:       python3-requests\nRequires:       python3-requests-pkcs12\nRequires:       python3-pysocks\nRequires:       python3-josepy\nRequires:       python3-acme\nRequires:       python3-xmltodict\nRequires:       python3-pyasn1\nRequires:       python3-pyasn1-modules\nRequires:       python3-pyyaml\n# Weak dependency for dataclasses - will install if available, won't fail if not\nRecommends:     python3-dataclasses\nRequires(post): policycoreutils\n\nBuildArch:\t\tnoarch\n\n\nSource0:        %{name}-%{version}.tar.gz\n\n%description\nacme2certifier is development project to create an ACME protocol proxy. Main intention is to provide ACME services on CA servers which do not support this protocol yet. It consists of two libraries:\n\n- acme_srv/*.py - a bunch of classes implementing ACME server functionality based on rfc8555\n- ca_handler.py - interface towards CA server. The intention of this library is to be\n  modular that an adaption to other CA servers should be straight forward. As of\n  today the following handlers are available:\n\n  - Openssl\n  - NetGuard Certificate Manager/Insta Certifier\n  - NetGuard Certificate Lifecycle Manager\n  - Generic EST protocol handler\n  - Generic CMPv2 protocol handler\n  - Microsoft Certificate Enrollment Web Services\n  - Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE) via RPC/DCOM\n  - Generic ACME protocol handler supporting Letsencrypt, BuyPass.com and ZeroSSL\n  - XCA\n  - acme2dfn (external; ACME proxy for the German research network's SOAP API)\n\nFor more up-to-date information and further documentation, please visit the project's\nhome page at: https://github.com/grindsa/acme2certifier\n\nRemember to:\n  - enable acme2certifer service\n\t  sudo systemctl enable acme2certifier.service\n\t  sudo systemctl start acme2certifier.service\n  - active acme2certifier in your nginx configuration\n\t  cp /opt/acme2certifer/examples/nginx/nginx_acme_srv[_ssl].conf /etc/nginx/conf.d\n  - enable and start nginx service\n\t  sudo systemctl enable nginx.service\n\t  sudo systemctl start nginx.service\n\n%prep\n%autosetup -p1 -n %{name}-%{?ghsha}%{?!ghsha:%{version}} -N\n\n%build\n# nothing to build\n\n\n%install\n# Main\n%{__mkdir_p} \\\n    %{buildroot}%{_datadir} \\\n    %{buildroot}%{_unitdir} \\\n    %{buildroot}%{dest_dir}/%{name}/examples \\\n\t%{buildroot}%{_docdir}/%{projname} \\\n    #\\\n    #%{buildroot}%{_sysconfdir}/httpd/conf.d \\\n\n# %{__cp} -a . %{buildroot}%{dest_dir}/%{projname}\n%{__cp} -a acme_srv tools %{buildroot}%{dest_dir}/%{projname}\n%{__cp} -a examples/ca_handler examples/db_handler examples/django examples/eab_handler examples/hooks examples/trigger examples/nginx %{buildroot}%{dest_dir}/%{projname}/examples\n\n%{__chmod} -R go-w %{buildroot}%{dest_dir}/%{projname}\n\n%{__cp} -a \\\n    examples/acme_srv.cfg \\\n    %{buildroot}%{dest_dir}/%{projname}/acme_srv/acme_srv.cfg\n\n%{__cp} -a \\\n    examples/db_handler/wsgi_handler.py \\\n    %{buildroot}%{dest_dir}/%{projname}/acme_srv/db_handler.py\n\n%{__cp} -a \\\n    examples/acme2certifier_wsgi.py \\\n    %{buildroot}%{dest_dir}/%{projname}/\n\n## Modify acme2certifier.ini for Redhat/Centos and derivations\n%{__sed} '\n$a\\\nplugins = python3\n' \\\n  examples/nginx/acme2certifier.ini > \\\n  %{buildroot}%{dest_dir}/%{projname}/acme2certifier.ini\n\n## Configure and enable uWSGI service\n# %{__sed} '\n# /^User/i\\\n# WorkingDirectory=%{dest_dir}/acme2certifier\n# ' \\\n#    examples/nginx/uwsgi.service > \\\n#    %{buildroot}%{_unitdir}/acme2certifier.service    # ugh\n\n# copy and rename service file\n%{__cp} -a \\\n    examples/nginx/uwsgi.service \\\n    %{buildroot}%{_unitdir}/acme2certifier.service\n\n%clean\n%{__chmod} -R 777 $RPM_BUILD_ROOT\n%{__rm} -rf $RPM_BUILD_ROOT\n\n\n%files\n%defattr(-,root,root,-)\n%config(noreplace) %{dest_dir}/%{projname}/acme_srv/acme_srv.cfg\n# %config(noreplace) %{dest_dir}/%{projname}/acme_srv/db_handler.py\n\n%license LICENSE\n%doc *.md requirements.txt docs/*.md\n%attr(0755,nginx,-)%{dest_dir}/%{projname}/\n%{_unitdir}/acme2certifier.service\n\n%changelog\n\n%post\nif [ -d %{dest_dir}/%{projname}/%{projname} ]; then\n    echo \"django environment detected\"\n    cp -R %{dest_dir}/%{projname}/examples/django/acme_srv/* %{dest_dir}/%{projname}/acme_srv/\n    cp -f %{dest_dir}/%{projname}/examples/db_handler/django_handler.py %{dest_dir}/%{projname}/acme_srv/db_handler.py\nfi\n\ncat <<EOT > /tmp/acme2certifier.te\nmodule acme2certifier 1.0;\n\nrequire {\n\ttype var_run_t;\n\ttype initrc_t;\n\ttype httpd_t;\n\tclass sock_file write;\n\tclass unix_stream_socket connectto;\n}\n\n#============= httpd_t ==============\nallow httpd_t initrc_t:unix_stream_socket connectto;\nallow httpd_t var_run_t:sock_file write;\nEOT\ncheckmodule -M -m -o /tmp/acme2certifier.mod /tmp/acme2certifier.te\nsemodule_package -o /tmp/acme2certifier.pp -m /tmp/acme2certifier.mod\nsemodule -i /tmp/acme2certifier.pp\nrm /tmp/acme2certifier.pp\nrm /tmp/acme2certifier.mod\nrm /tmp/acme2certifier.te\n"
  },
  {
    "path": "examples/nginx/acme2certifier.ini",
    "content": "[uwsgi]\nmodule = acme2certifier_wsgi:application\nmaster = true\nprocesses = 5\nuid = nginx\nsocket = /run/uwsgi/acme.sock\nchown-socket = nginx\nchmod-socket = 660\nvacuum = true\ndie-on-term = true\ndisable-logging = true\nenable-threads = true\n"
  },
  {
    "path": "examples/nginx/acme2certifier.te",
    "content": "\nmodule acme2certifier 1.0;\n\nrequire {\n\ttype var_run_t;\n\ttype initrc_t;\n\ttype httpd_t;\n\tclass sock_file write;\n\tclass unix_stream_socket connectto;\n}\n\n#============= httpd_t ==============\nallow httpd_t initrc_t:unix_stream_socket connectto;\nallow httpd_t var_run_t:sock_file write;\n"
  },
  {
    "path": "examples/nginx/nginx_acme_srv.conf",
    "content": "# zone with 10mb memory which is 160k/s - 5requests per client per second\nlimit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;\n\nserver {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n\n    # first 5 requests go trough instantly 5more requests evey 100ms\n    limit_req zone=ip burst=10; # delay=5;\n\n    server_name _;\n    location = favicon.ico { access_log off; log_not_found off; }\n    location / {\n        include uwsgi_params;\n        uwsgi_pass unix:/run/uwsgi/acme.sock;\n        if ($request_method = \"HEAD\" ) {\n           add_header Content-length 0;\n           # add_header Transfer-Encoding identity;\n        }\n   }\n}\n"
  },
  {
    "path": "examples/nginx/nginx_acme_srv_ssl.conf",
    "content": "# zone with 10mb memory which is 160k/s - 5requests per client per second\n# limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;\nserver {\n    listen 443 ssl default_server;\n    listen [::]:443 ssl default_server;\n\n    # first 5 requests go trough instantly 5more requests evey 100ms\n    limit_req zone=ip burst=10; # delay=5;\n\n    server_name _;\n    location = favicon.ico { access_log off; log_not_found off; }\n    location / {\n        include uwsgi_params;\n        uwsgi_pass unix:/run/uwsgi/acme.sock;\n        if ($request_method = \"HEAD\" ) {\n           add_header Content-length 0;\n           # add_header Transfer-Encoding identity;\n        }\n   }\n   ssl_certificate \"/var/www/acme2certifier/volume/acme2certifier_cert.pem\";\n   ssl_certificate_key \"/var/www/acme2certifier/volume/acme2certifier_key.pem\";\n   ssl_session_cache shared:SSL:1m;\n   ssl_session_timeout  10m;\n   ssl_ciphers \"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH\";\n   ssl_prefer_server_ciphers on;\n}\n"
  },
  {
    "path": "examples/nginx/supervisord.conf",
    "content": "# /etc/supervisord.conf for nginx in docker\n[supervisord]\nnodaemon=true\n\n[program:uwsgi]\ncommand=/usr/bin/uwsgi_python312 --ini /var/www/acme2certifier/acme2certifier.ini\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[program:nginx]\ncommand=/usr/sbin/nginx -g \"daemon off;\"\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n"
  },
  {
    "path": "examples/nginx/uwsgi.service",
    "content": "[Unit]\nDescription=uWSGI instance to serve acme2certifier\n\n[Service]\nRuntimeDirectory=uwsgi\nExecStart=uwsgi --ini acme2certifier.ini\nWorkingDirectory=/opt/acme2certifier\nRestart=always\nType=notify\nNotifyAccess=all\nUser=nginx\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/reports/account_report.csv",
    "content": "account.id,account.name,account.eab_kid,account.contact,account.created_at,account.jwk,account.alg,order.id,order.name,order.status.id,order.status.name,order.notbefore,order.notafter,order.expires,order.identifiers,authorization.id,authorization.name,authorization.type,authorization.value,authorization.expires,authorization.token,authorization.created_at,authorization.status.id,authorization.status.name,challenge.id,challenge.name,challenge.token,challenge.expires,challenge.type,challenge.keyauthorization,challenge.created_at,challenge.status.id,challenge.status.name\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,1,fBK7HtG3916w,1,invalid,,,2020-07-31 14:53:11,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",1,Jd99c5z2Imxq,dns,foo1.bar.local,2020-07-31 14:53:12,- removed - ,2020-07-30 14:53:11,6,expired,1,O4sbT90W92lx,- removed -,2020-07-31 14:53:12,http-01,,2020-07-30 14:53:12,1,invalid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,1,fBK7HtG3916w,1,invalid,,,2020-07-31 14:53:11,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",1,Jd99c5z2Imxq,dns,foo1.bar.local,2020-07-31 14:53:12,- removed - ,2020-07-30 14:53:11,6,expired,2,pHqvViegHHdy,- removed -,2020-07-31 14:53:12,dns-01,,2020-07-30 14:53:12,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,1,fBK7HtG3916w,1,invalid,,,2020-07-31 14:53:11,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",2,k47Ddfr7Moim,dns,foo2.bar.local,2020-07-31 14:53:12,- removed - ,2020-07-30 14:53:11,6,expired,3,el9cfdFOWee3,- removed -,2020-07-31 14:53:12,http-01,,2020-07-30 14:53:12,1,invalid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,1,fBK7HtG3916w,1,invalid,,,2020-07-31 14:53:11,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",2,k47Ddfr7Moim,dns,foo2.bar.local,2020-07-31 14:53:12,- removed - ,2020-07-30 14:53:11,6,expired,4,bLDhYDIhFAMz,- removed -,2020-07-31 14:53:12,dns-01,,2020-07-30 14:53:12,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,2,ssg6hitaZleN,1,invalid,,,2020-07-31 14:54:01,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",3,yeh0SqUSirhX,dns,foo1.bar.local,2020-07-31 14:54:01,- removed - ,2020-07-30 14:54:01,6,expired,5,wCHIrOwo95Rg,- removed -,2020-07-31 14:54:01,http-01,,2020-07-30 14:54:01,1,invalid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,2,ssg6hitaZleN,1,invalid,,,2020-07-31 14:54:01,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",3,yeh0SqUSirhX,dns,foo1.bar.local,2020-07-31 14:54:01,- removed - ,2020-07-30 14:54:01,6,expired,6,6heNowhBOznl,- removed -,2020-07-31 14:54:01,dns-01,,2020-07-30 14:54:01,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,2,ssg6hitaZleN,1,invalid,,,2020-07-31 14:54:01,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",4,cGDmzBkX8DOf,dns,foo2.bar.local,2020-07-31 14:54:02,- removed - ,2020-07-30 14:54:01,6,expired,7,dlg8GJHTph7J,- removed -,2020-07-31 14:54:02,http-01,,2020-07-30 14:54:02,1,invalid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,2,ssg6hitaZleN,1,invalid,,,2020-07-31 14:54:01,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",4,cGDmzBkX8DOf,dns,foo2.bar.local,2020-07-31 14:54:02,- removed - ,2020-07-30 14:54:01,6,expired,8,A4UUZEXkuOrZ,- removed -,2020-07-31 14:54:02,dns-01,,2020-07-30 14:54:02,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,3,C0I8rUgyHY9c,1,invalid,,,2020-07-31 14:54:46,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",5,bJSN4MhrgJnk,dns,foo1.bar.local,2020-07-31 14:54:46,- removed - ,2020-07-30 14:54:46,6,expired,9,vAFz8BARNdor,- removed -,2020-07-31 14:54:46,http-01,,2020-07-30 14:54:46,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,3,C0I8rUgyHY9c,1,invalid,,,2020-07-31 14:54:46,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",5,bJSN4MhrgJnk,dns,foo1.bar.local,2020-07-31 14:54:46,- removed - ,2020-07-30 14:54:46,6,expired,10,XOxUp97gAKN6,- removed -,2020-07-31 14:54:46,dns-01,,2020-07-30 14:54:46,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,3,C0I8rUgyHY9c,1,invalid,,,2020-07-31 14:54:46,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",6,ja4oRRMZODjs,dns,foo2.bar.local,2020-07-31 14:54:46,- removed - ,2020-07-30 14:54:46,6,expired,11,GNR6Kb5aFLN8,- removed -,2020-07-31 14:54:46,http-01,,2020-07-30 14:54:46,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,3,C0I8rUgyHY9c,1,invalid,,,2020-07-31 14:54:46,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",6,ja4oRRMZODjs,dns,foo2.bar.local,2020-07-31 14:54:46,- removed - ,2020-07-30 14:54:46,6,expired,12,9A93NLQ6HhMm,- removed -,2020-07-31 14:54:46,dns-01,,2020-07-30 14:54:46,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,4,SkcFonuJQH7p,1,invalid,,,2020-07-31 14:55:44,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",7,DqQ471vq6wl1,dns,foo1.bar.local,2020-07-31 14:55:44,- removed - ,2020-07-30 14:55:44,6,expired,13,dpt3CV2M2Oms,- removed -,2020-07-31 14:55:44,http-01,,2020-07-30 14:55:44,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,4,SkcFonuJQH7p,1,invalid,,,2020-07-31 14:55:44,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",7,DqQ471vq6wl1,dns,foo1.bar.local,2020-07-31 14:55:44,- removed - ,2020-07-30 14:55:44,6,expired,14,4dxkoXFubegB,- removed -,2020-07-31 14:55:44,dns-01,,2020-07-30 14:55:44,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,4,SkcFonuJQH7p,1,invalid,,,2020-07-31 14:55:44,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",8,rex88FoMoseg,dns,foo2.bar.local,2020-07-31 14:55:44,- removed - ,2020-07-30 14:55:44,6,expired,15,N1GyaWgXPMPl,- removed -,2020-07-31 14:55:44,http-01,,2020-07-30 14:55:44,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,4,SkcFonuJQH7p,1,invalid,,,2020-07-31 14:55:44,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",8,rex88FoMoseg,dns,foo2.bar.local,2020-07-31 14:55:44,- removed - ,2020-07-30 14:55:44,6,expired,16,lLiOl4q4SvCe,- removed -,2020-07-31 14:55:44,dns-01,,2020-07-30 14:55:44,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",9,SD8qRrTIfhDH,dns,foo1.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,17,UdG5vCrPDLCW,- removed -,2020-07-31 14:56:23,http-01,,2020-07-30 14:56:23,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",9,SD8qRrTIfhDH,dns,foo1.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,18,3Z5NjuedCXLT,- removed -,2020-07-31 14:56:23,dns-01,,2020-07-30 14:56:23,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",10,NaEtaOh24PkW,dns,foo2.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,19,qbnIM61sjq2u,- removed -,2020-07-31 14:56:23,http-01,,2020-07-30 14:56:23,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",10,NaEtaOh24PkW,dns,foo2.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,20,o4l2RDVN96Vy,- removed -,2020-07-31 14:56:23,dns-01,,2020-07-30 14:56:23,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",11,aTI19nbBWcl5,dns,foo3.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,21,nNyFNbvttYNy,- removed -,2020-07-31 14:56:23,http-01,,2020-07-30 14:56:23,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,5,tDXY56holtPf,1,invalid,,,2020-07-31 14:56:22,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",11,aTI19nbBWcl5,dns,foo3.bar.local,2020-07-31 14:56:23,- removed - ,2020-07-30 14:56:22,6,expired,22,y1F0Ab1vdmgk,- removed -,2020-07-31 14:56:23,dns-01,,2020-07-30 14:56:23,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,6,pMqVtHqIkT64,1,invalid,,,2020-07-31 14:57:24,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo4.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo5.bar.local\"\"}]\",12,dhuPBAN2acYV,dns,foo4.bar.local,2020-07-31 14:57:24,- removed - ,2020-07-30 14:57:24,6,expired,23,btH5RRDB0NnX,- removed -,2020-07-31 14:57:24,http-01,,2020-07-30 14:57:25,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,6,pMqVtHqIkT64,1,invalid,,,2020-07-31 14:57:24,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo4.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo5.bar.local\"\"}]\",12,dhuPBAN2acYV,dns,foo4.bar.local,2020-07-31 14:57:24,- removed - ,2020-07-30 14:57:24,6,expired,24,nJc2ceZEevYq,- removed -,2020-07-31 14:57:24,dns-01,,2020-07-30 14:57:25,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,6,pMqVtHqIkT64,1,invalid,,,2020-07-31 14:57:24,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo4.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo5.bar.local\"\"}]\",13,OVp6CeUbHoov,dns,foo5.bar.local,2020-07-31 14:57:25,- removed - ,2020-07-30 14:57:24,6,expired,25,euzAbGxVYT7J,- removed -,2020-07-31 14:57:25,http-01,,2020-07-30 14:57:25,5,valid\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,6,pMqVtHqIkT64,1,invalid,,,2020-07-31 14:57:24,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo4.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo5.bar.local\"\"}]\",13,OVp6CeUbHoov,dns,foo5.bar.local,2020-07-31 14:57:25,- removed - ,2020-07-30 14:57:24,6,expired,26,uJ87vcrc79Om,- removed -,2020-07-31 14:57:25,dns-01,,2020-07-30 14:57:25,2,pending\n2,brykxxUN24fZ,,\"[\"\"mailto:certbot@bar.local\"\"]\",2020-07-30 14:58:26,\"{\"\"e\"\": \"\"AQAB\"\", \"\"kty\"\": \"\"RSA\"\", \"\"n\"\": \"\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\"\"}\",RS256,7,bZU4xme1P11D,1,invalid,,,2020-07-31 14:58:49,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-2.bar.local\"\"}]\",14,CFzsMUGL5hRp,dns,certbot-1.bar.local,2020-07-31 14:58:51,- removed - ,2020-07-30 14:58:49,6,expired,27,GYAQs7xdLgtz,- removed -,2020-07-31 14:58:50,http-01,,2020-07-30 14:58:50,5,valid\n2,brykxxUN24fZ,,\"[\"\"mailto:certbot@bar.local\"\"]\",2020-07-30 14:58:26,\"{\"\"e\"\": \"\"AQAB\"\", \"\"kty\"\": \"\"RSA\"\", \"\"n\"\": \"\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\"\"}\",RS256,7,bZU4xme1P11D,1,invalid,,,2020-07-31 14:58:49,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-2.bar.local\"\"}]\",14,CFzsMUGL5hRp,dns,certbot-1.bar.local,2020-07-31 14:58:51,- removed - ,2020-07-30 14:58:49,6,expired,28,I2VWmbwJCtei,- removed -,2020-07-31 14:58:50,dns-01,,2020-07-30 14:58:50,2,pending\n2,brykxxUN24fZ,,\"[\"\"mailto:certbot@bar.local\"\"]\",2020-07-30 14:58:26,\"{\"\"e\"\": \"\"AQAB\"\", \"\"kty\"\": \"\"RSA\"\", \"\"n\"\": \"\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\"\"}\",RS256,7,bZU4xme1P11D,1,invalid,,,2020-07-31 14:58:49,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-2.bar.local\"\"}]\",15,JWj1arPPwAbc,dns,certbot-2.bar.local,2020-07-31 14:58:51,- removed - ,2020-07-30 14:58:49,6,expired,29,oo5v1a516Ulr,- removed -,2020-07-31 14:58:50,http-01,,2020-07-30 14:58:50,5,valid\n2,brykxxUN24fZ,,\"[\"\"mailto:certbot@bar.local\"\"]\",2020-07-30 14:58:26,\"{\"\"e\"\": \"\"AQAB\"\", \"\"kty\"\": \"\"RSA\"\", \"\"n\"\": \"\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\"\"}\",RS256,7,bZU4xme1P11D,1,invalid,,,2020-07-31 14:58:49,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-2.bar.local\"\"}]\",15,JWj1arPPwAbc,dns,certbot-2.bar.local,2020-07-31 14:58:51,- removed - ,2020-07-30 14:58:49,6,expired,30,eVjIudlTD4CE,- removed -,2020-07-31 14:58:50,dns-01,,2020-07-30 14:58:50,2,pending\n1,QMSCUs0MH1j1,kid01,\"[\"\"mailto:grindsa3@bar.local\"\"]\",2020-07-30 14:52:20,\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",ES256,8,BbYOopY9ged0,1,invalid,,,2020-07-31 15:02:09,\"[{\"\"type\"\": \"\"TNAuthList\"\", \"\"value\"\": \"\"- removed -\"\"}]\",16,GIcD3kJS3Pcz,TNAuthList,MAqgCBYGMTIzNDU2,2020-07-31 15:02:10,- removed - ,2020-07-30 15:02:09,6,expired,31,M5OQvzn7t7eu,- removed -,2020-07-31 15:02:10,tkauth-01,,2020-07-30 15:02:10,5,valid\n3,Z9cKiTD0n3No,eabid_lego,\"[\"\"mailto:lego@bar.local\"\"]\",2020-07-30 15:03:56,\"{\"\"kty\"\": \"\"EC\"\", \"\"crv\"\": \"\"P-384\"\", \"\"x\"\": \"\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\"\", \"\"y\"\": \"\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\"\"}\",ES384,9,AscxIwNhKwpm,1,invalid,,,2020-07-31 15:03:56,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-2.bar.local\"\"}]\",17,nkPCNsD6N1rj,dns,lego-1.bar.local,2020-07-31 15:03:56,- removed - ,2020-07-30 15:03:56,6,expired,32,a4cfxcjTKIQp,- removed -,2020-07-31 15:03:56,http-01,,2020-07-30 15:03:56,5,valid\n3,Z9cKiTD0n3No,eabid_lego,\"[\"\"mailto:lego@bar.local\"\"]\",2020-07-30 15:03:56,\"{\"\"kty\"\": \"\"EC\"\", \"\"crv\"\": \"\"P-384\"\", \"\"x\"\": \"\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\"\", \"\"y\"\": \"\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\"\"}\",ES384,9,AscxIwNhKwpm,1,invalid,,,2020-07-31 15:03:56,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-2.bar.local\"\"}]\",17,nkPCNsD6N1rj,dns,lego-1.bar.local,2020-07-31 15:03:56,- removed - ,2020-07-30 15:03:56,6,expired,33,HNcwNH41KZBc,- removed -,2020-07-31 15:03:56,dns-01,,2020-07-30 15:03:56,2,pending\n3,Z9cKiTD0n3No,eabid_lego,\"[\"\"mailto:lego@bar.local\"\"]\",2020-07-30 15:03:56,\"{\"\"kty\"\": \"\"EC\"\", \"\"crv\"\": \"\"P-384\"\", \"\"x\"\": \"\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\"\", \"\"y\"\": \"\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\"\"}\",ES384,9,AscxIwNhKwpm,1,invalid,,,2020-07-31 15:03:56,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-2.bar.local\"\"}]\",18,Wpv7PkfBANya,dns,lego-2.bar.local,2020-07-31 15:03:56,- removed - ,2020-07-30 15:03:56,6,expired,34,vogQnDpEHVXi,- removed -,2020-07-31 15:03:56,http-01,,2020-07-30 15:03:56,5,valid\n3,Z9cKiTD0n3No,eabid_lego,\"[\"\"mailto:lego@bar.local\"\"]\",2020-07-30 15:03:56,\"{\"\"kty\"\": \"\"EC\"\", \"\"crv\"\": \"\"P-384\"\", \"\"x\"\": \"\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\"\", \"\"y\"\": \"\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\"\"}\",ES384,9,AscxIwNhKwpm,1,invalid,,,2020-07-31 15:03:56,\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-2.bar.local\"\"}]\",18,Wpv7PkfBANya,dns,lego-2.bar.local,2020-07-31 15:03:56,- removed - ,2020-07-30 15:03:56,6,expired,35,lT0WBBX25PwF,- removed -,2020-07-31 15:03:56,dns-01,,2020-07-30 15:03:56,2,pending\n"
  },
  {
    "path": "examples/reports/account_report.json",
    "content": "[\n    {\n        \"account.id\": 1,\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.eab_kid\": \"kid01\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"orders\": [\n            {\n                \"order.id\": 1,\n                \"order.name\": \"fBK7HtG3916w\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:53:11\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 1,\n                        \"authorization.name\": \"Jd99c5z2Imxq\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:53:12\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:53:11\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 1,\n                                \"challenge.name\": \"O4sbT90W92lx\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 2,\n                                \"challenge.name\": \"pHqvViegHHdy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 2,\n                        \"authorization.name\": \"k47Ddfr7Moim\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:53:12\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:53:11\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 3,\n                                \"challenge.name\": \"el9cfdFOWee3\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 4,\n                                \"challenge.name\": \"bLDhYDIhFAMz\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 2,\n                \"order.name\": \"ssg6hitaZleN\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:54:01\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 3,\n                        \"authorization.name\": \"yeh0SqUSirhX\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:01\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:01\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 5,\n                                \"challenge.name\": \"wCHIrOwo95Rg\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:01\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:01\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 6,\n                                \"challenge.name\": \"6heNowhBOznl\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:01\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:01\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 4,\n                        \"authorization.name\": \"cGDmzBkX8DOf\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:02\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:01\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 7,\n                                \"challenge.name\": \"dlg8GJHTph7J\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:02\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:02\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 8,\n                                \"challenge.name\": \"A4UUZEXkuOrZ\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:02\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:02\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 3,\n                \"order.name\": \"C0I8rUgyHY9c\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:54:46\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 5,\n                        \"authorization.name\": \"bJSN4MhrgJnk\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:46\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:46\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 9,\n                                \"challenge.name\": \"vAFz8BARNdor\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            },\n                            {\n                                \"challenge.id\": 10,\n                                \"challenge.name\": \"XOxUp97gAKN6\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 6,\n                        \"authorization.name\": \"ja4oRRMZODjs\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:46\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:46\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 11,\n                                \"challenge.name\": \"GNR6Kb5aFLN8\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            },\n                            {\n                                \"challenge.id\": 12,\n                                \"challenge.name\": \"9A93NLQ6HhMm\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 4,\n                \"order.name\": \"SkcFonuJQH7p\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:55:44\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 7,\n                        \"authorization.name\": \"DqQ471vq6wl1\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:55:44\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:55:44\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 13,\n                                \"challenge.name\": \"dpt3CV2M2Oms\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 14,\n                                \"challenge.name\": \"4dxkoXFubegB\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 8,\n                        \"authorization.name\": \"rex88FoMoseg\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:55:44\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:55:44\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 15,\n                                \"challenge.name\": \"N1GyaWgXPMPl\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 16,\n                                \"challenge.name\": \"lLiOl4q4SvCe\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 5,\n                \"order.name\": \"tDXY56holtPf\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:56:22\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo3.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 9,\n                        \"authorization.name\": \"SD8qRrTIfhDH\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 17,\n                                \"challenge.name\": \"UdG5vCrPDLCW\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 18,\n                                \"challenge.name\": \"3Z5NjuedCXLT\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 10,\n                        \"authorization.name\": \"NaEtaOh24PkW\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 19,\n                                \"challenge.name\": \"qbnIM61sjq2u\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 20,\n                                \"challenge.name\": \"o4l2RDVN96Vy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 11,\n                        \"authorization.name\": \"aTI19nbBWcl5\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo3.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 21,\n                                \"challenge.name\": \"nNyFNbvttYNy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 22,\n                                \"challenge.name\": \"y1F0Ab1vdmgk\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 6,\n                \"order.name\": \"pMqVtHqIkT64\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:57:24\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo4.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo5.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 12,\n                        \"authorization.name\": \"dhuPBAN2acYV\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo4.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:57:24\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:57:24\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 23,\n                                \"challenge.name\": \"btH5RRDB0NnX\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:24\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 24,\n                                \"challenge.name\": \"nJc2ceZEevYq\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:24\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 13,\n                        \"authorization.name\": \"OVp6CeUbHoov\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo5.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:57:25\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:57:24\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 25,\n                                \"challenge.name\": \"euzAbGxVYT7J\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:25\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 26,\n                                \"challenge.name\": \"uJ87vcrc79Om\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:25\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 8,\n                \"order.name\": \"BbYOopY9ged0\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 15:02:09\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"TNAuthList\\\", \\\"value\\\": \\\"MAqgCBYGMTIzNDU2\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 16,\n                        \"authorization.name\": \"GIcD3kJS3Pcz\",\n                        \"authorization.type\": \"TNAuthList\",\n                        \"authorization.value\": \"- removed - \",\n                        \"authorization.expires\": \"2020-07-31 15:02:10\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:02:09\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 31,\n                                \"challenge.name\": \"M5OQvzn7t7eu\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:02:10\",\n                                \"challenge.type\": \"tkauth-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:02:10\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    },\n    {\n        \"account.id\": 2,\n        \"account.name\": \"brykxxUN24fZ\",\n        \"account.eab_kid\": \"\",\n        \"account.contact\": \"[\\\"mailto:certbot@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:58:26\",\n        \"account.jwk\": \"{\\\"e\\\": \\\"AQAB\\\", \\\"kty\\\": \\\"RSA\\\", \\\"n\\\": \\\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\\\"}\",\n        \"account.alg\": \"RS256\",\n        \"orders\": [\n            {\n                \"order.id\": 7,\n                \"order.name\": \"bZU4xme1P11D\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:58:49\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 14,\n                        \"authorization.name\": \"CFzsMUGL5hRp\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"certbot-1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:58:51\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:58:49\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 27,\n                                \"challenge.name\": \"GYAQs7xdLgtz\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 28,\n                                \"challenge.name\": \"I2VWmbwJCtei\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 15,\n                        \"authorization.name\": \"JWj1arPPwAbc\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"certbot-2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:58:51\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:58:49\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 29,\n                                \"challenge.name\": \"oo5v1a516Ulr\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 30,\n                                \"challenge.name\": \"eVjIudlTD4CE\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    },\n    {\n        \"account.id\": 3,\n        \"account.name\": \"Z9cKiTD0n3No\",\n        \"account.eab_kid\": \"eabid_lego\",\n        \"account.contact\": \"[\\\"mailto:lego@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 15:03:56\",\n        \"account.jwk\": \"{\\\"kty\\\": \\\"EC\\\", \\\"crv\\\": \\\"P-384\\\", \\\"x\\\": \\\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\\\", \\\"y\\\": \\\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\\\"}\",\n        \"account.alg\": \"ES384\",\n        \"orders\": [\n            {\n                \"order.id\": 9,\n                \"order.name\": \"AscxIwNhKwpm\",\n                \"order.status.id\": 1,\n                \"order.status.name\": \"invalid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 15:03:56\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 17,\n                        \"authorization.name\": \"nkPCNsD6N1rj\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"lego-1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 15:03:56\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:03:56\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 32,\n                                \"challenge.name\": \"a4cfxcjTKIQp\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 33,\n                                \"challenge.name\": \"HNcwNH41KZBc\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 18,\n                        \"authorization.name\": \"Wpv7PkfBANya\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"lego-2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 15:03:56\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:03:56\",\n                        \"authorization.status.id\": 6,\n                        \"authorization.status.name\": \"expired\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 34,\n                                \"challenge.name\": \"vogQnDpEHVXi\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 35,\n                                \"challenge.name\": \"lT0WBBX25PwF\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "examples/reports/account_report_nested.json",
    "content": "[\n    {\n        \"account.id\": 1,\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"orders\": [\n            {\n                \"order.id\": 1,\n                \"order.name\": \"fBK7HtG3916w\",\n                \"order.status.id\": 2,\n                \"order.status.name\": \"pending\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:53:11\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 1,\n                        \"authorization.name\": \"Jd99c5z2Imxq\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:53:12\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:53:11\",\n                        \"authorization.status.id\": 1,\n                        \"authorization.status.name\": \"invalid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 1,\n                                \"challenge.name\": \"O4sbT90W92lx\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 2,\n                                \"challenge.name\": \"pHqvViegHHdy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 2,\n                        \"authorization.name\": \"k47Ddfr7Moim\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:53:12\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:53:11\",\n                        \"authorization.status.id\": 1,\n                        \"authorization.status.name\": \"invalid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 3,\n                                \"challenge.name\": \"el9cfdFOWee3\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 4,\n                                \"challenge.name\": \"bLDhYDIhFAMz\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:53:12\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:53:12\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 2,\n                \"order.name\": \"ssg6hitaZleN\",\n                \"order.status.id\": 2,\n                \"order.status.name\": \"pending\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:54:01\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 3,\n                        \"authorization.name\": \"yeh0SqUSirhX\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:01\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:01\",\n                        \"authorization.status.id\": 1,\n                        \"authorization.status.name\": \"invalid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 5,\n                                \"challenge.name\": \"wCHIrOwo95Rg\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:01\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:01\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 6,\n                                \"challenge.name\": \"6heNowhBOznl\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:01\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:01\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 4,\n                        \"authorization.name\": \"cGDmzBkX8DOf\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:02\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:01\",\n                        \"authorization.status.id\": 1,\n                        \"authorization.status.name\": \"invalid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 7,\n                                \"challenge.name\": \"dlg8GJHTph7J\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:02\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:02\",\n                                \"challenge.status.id\": 1,\n                                \"challenge.status.name\": \"invalid\"\n                            },\n                            {\n                                \"challenge.id\": 8,\n                                \"challenge.name\": \"A4UUZEXkuOrZ\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:02\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:02\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 3,\n                \"order.name\": \"C0I8rUgyHY9c\",\n                \"order.status.id\": 2,\n                \"order.status.name\": \"pending\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:54:46\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 5,\n                        \"authorization.name\": \"bJSN4MhrgJnk\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:46\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:46\",\n                        \"authorization.status.id\": 2,\n                        \"authorization.status.name\": \"pending\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 9,\n                                \"challenge.name\": \"vAFz8BARNdor\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            },\n                            {\n                                \"challenge.id\": 10,\n                                \"challenge.name\": \"XOxUp97gAKN6\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 6,\n                        \"authorization.name\": \"ja4oRRMZODjs\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:54:46\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:54:46\",\n                        \"authorization.status.id\": 2,\n                        \"authorization.status.name\": \"pending\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 11,\n                                \"challenge.name\": \"GNR6Kb5aFLN8\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            },\n                            {\n                                \"challenge.id\": 12,\n                                \"challenge.name\": \"9A93NLQ6HhMm\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:54:46\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:54:46\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 4,\n                \"order.name\": \"SkcFonuJQH7p\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:55:44\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 7,\n                        \"authorization.name\": \"DqQ471vq6wl1\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:55:44\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:55:44\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 13,\n                                \"challenge.name\": \"dpt3CV2M2Oms\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 14,\n                                \"challenge.name\": \"4dxkoXFubegB\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 8,\n                        \"authorization.name\": \"rex88FoMoseg\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:55:44\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:55:44\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 15,\n                                \"challenge.name\": \"N1GyaWgXPMPl\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 16,\n                                \"challenge.name\": \"lLiOl4q4SvCe\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:55:44\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:55:44\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 5,\n                \"order.name\": \"tDXY56holtPf\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:56:22\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo3.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 9,\n                        \"authorization.name\": \"SD8qRrTIfhDH\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 17,\n                                \"challenge.name\": \"UdG5vCrPDLCW\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 18,\n                                \"challenge.name\": \"3Z5NjuedCXLT\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 10,\n                        \"authorization.name\": \"NaEtaOh24PkW\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 19,\n                                \"challenge.name\": \"qbnIM61sjq2u\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 20,\n                                \"challenge.name\": \"o4l2RDVN96Vy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 11,\n                        \"authorization.name\": \"aTI19nbBWcl5\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo3.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:56:23\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:56:22\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 21,\n                                \"challenge.name\": \"nNyFNbvttYNy\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 22,\n                                \"challenge.name\": \"y1F0Ab1vdmgk\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:56:23\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:56:23\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 6,\n                \"order.name\": \"pMqVtHqIkT64\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:57:24\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo4.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo5.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 12,\n                        \"authorization.name\": \"dhuPBAN2acYV\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo4.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:57:24\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:57:24\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 23,\n                                \"challenge.name\": \"btH5RRDB0NnX\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:24\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 24,\n                                \"challenge.name\": \"nJc2ceZEevYq\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:24\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 13,\n                        \"authorization.name\": \"OVp6CeUbHoov\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"foo5.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:57:25\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:57:24\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 25,\n                                \"challenge.name\": \"euzAbGxVYT7J\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:25\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 26,\n                                \"challenge.name\": \"uJ87vcrc79Om\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:57:25\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:57:25\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            },\n            {\n                \"order.id\": 8,\n                \"order.name\": \"BbYOopY9ged0\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 15:02:09\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"TNAuthList\\\", \\\"value\\\": \\\"MAqgCBYGMTIzNDU2\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 16,\n                        \"authorization.name\": \"GIcD3kJS3Pcz\",\n                        \"authorization.type\": \"TNAuthList\",\n                        \"authorization.value\": \"- removed - \",\n                        \"authorization.expires\": \"2020-07-31 15:02:10\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:02:09\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 31,\n                                \"challenge.name\": \"M5OQvzn7t7eu\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:02:10\",\n                                \"challenge.type\": \"tkauth-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:02:10\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    },\n    {\n        \"account.id\": 2,\n        \"account.name\": \"brykxxUN24fZ\",\n        \"account.contact\": \"[\\\"mailto:certbot@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:58:26\",\n        \"account.jwk\": \"{\\\"e\\\": \\\"AQAB\\\", \\\"kty\\\": \\\"RSA\\\", \\\"n\\\": \\\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\\\"}\",\n        \"account.alg\": \"RS256\",\n        \"orders\": [\n            {\n                \"order.id\": 7,\n                \"order.name\": \"bZU4xme1P11D\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 14:58:49\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 14,\n                        \"authorization.name\": \"CFzsMUGL5hRp\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"certbot-1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:58:51\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:58:49\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 27,\n                                \"challenge.name\": \"GYAQs7xdLgtz\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 28,\n                                \"challenge.name\": \"I2VWmbwJCtei\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 15,\n                        \"authorization.name\": \"JWj1arPPwAbc\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"certbot-2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 14:58:51\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 14:58:49\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 29,\n                                \"challenge.name\": \"oo5v1a516Ulr\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 30,\n                                \"challenge.name\": \"eVjIudlTD4CE\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 14:58:50\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 14:58:50\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    },\n    {\n        \"account.id\": 3,\n        \"account.name\": \"Z9cKiTD0n3No\",\n        \"account.contact\": \"[\\\"mailto:lego@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 15:03:56\",\n        \"account.jwk\": \"{\\\"kty\\\": \\\"EC\\\", \\\"crv\\\": \\\"P-384\\\", \\\"x\\\": \\\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\\\", \\\"y\\\": \\\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\\\"}\",\n        \"account.alg\": \"ES384\",\n        \"orders\": [\n            {\n                \"order.id\": 9,\n                \"order.name\": \"AscxIwNhKwpm\",\n                \"order.status.id\": 5,\n                \"order.status.name\": \"valid\",\n                \"order.notbefore\": \"\",\n                \"order.notafter\": \"\",\n                \"order.expires\": \"2020-07-31 15:03:56\",\n                \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-2.bar.local\\\"}]\",\n                \"authorizations\": [\n                    {\n                        \"authorization.id\": 17,\n                        \"authorization.name\": \"nkPCNsD6N1rj\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"lego-1.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 15:03:56\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:03:56\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 32,\n                                \"challenge.name\": \"a4cfxcjTKIQp\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 33,\n                                \"challenge.name\": \"HNcwNH41KZBc\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    },\n                    {\n                        \"authorization.id\": 18,\n                        \"authorization.name\": \"Wpv7PkfBANya\",\n                        \"authorization.type\": \"dns\",\n                        \"authorization.value\": \"lego-2.bar.local\",\n                        \"authorization.expires\": \"2020-07-31 15:03:56\",\n                        \"authorization.token\": \"- removed - \",\n                        \"authorization.created_at\": \"2020-07-30 15:03:56\",\n                        \"authorization.status.id\": 5,\n                        \"authorization.status.name\": \"valid\",\n                        \"challenges\": [\n                            {\n                                \"challenge.id\": 34,\n                                \"challenge.name\": \"vogQnDpEHVXi\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"http-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 5,\n                                \"challenge.status.name\": \"valid\"\n                            },\n                            {\n                                \"challenge.id\": 35,\n                                \"challenge.name\": \"lT0WBBX25PwF\",\n                                \"challenge.token\": \"- removed -\",\n                                \"challenge.expires\": \"2020-07-31 15:03:56\",\n                                \"challenge.type\": \"dns-01\",\n                                \"challenge.keyauthorization\": null,\n                                \"challenge.created_at\": \"2020-07-30 15:03:56\",\n                                \"challenge.status.id\": 2,\n                                \"challenge.status.name\": \"pending\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "examples/reports/cert_report.csv",
    "content": "\"certificate.id\",\"certificate.name\",\"certificate.serial\",\"certificate.cert_raw\",\"certificate.csr\",\"certificate.poll_identifier\",\"certificate.created_at\",\"certificate.issue_date\",\"certificate.expire_date\",\"certificate.issue_uts\",\"certificate.expire_uts\",\"order.id\",\"order.name\",\"order.status.name\",\"order.notbefore\",\"order.notafter\",\"order.expires\",\"order.identifiers\",\"account.name\",\"account.contact\",\"account.created_at\",\"account.jwk\",\"account.alg\"\n1,\"i0hhZkP34dKf\",4397893961547615456,\"MIIEfjCCAmagAwIBAgIIPQh1QcnhjOAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU1NTJaFw0yMTA3MzAxNDU1NTJaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvj82IA6aQZT9E1DTdnqT2Kb9/HF2YWbldMlnGT2xRvLa/WaMpRM/EgmkcNnhAnf4FMTjv75iDTBKrTSP2+jnWAtTS7XDbYPwwgHOTCv42JIwj0MmuP2KiCnglM2nVtUNxNWMWB6wQNeAAxZFNon1zGjj9OP7d1HZRDb9BZO707Cxd3FfXzWoJLmpDVHhspOhy2Z65qAXATpgkyIhKYN3IeUVmn+xhAn7tv/Q9YIW+JJOaE9/qwVjgE9yGY55YonO1mnxgimGe2Jcb/CARwMGl0mL6yRAe/R2KEcSF34eWE9EPzff4rDrRIUNdG3q1geMLEP6QrreR+m6GA79dQiK2QIDAQABo4G4MIG1MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwHQYDVR0OBBYEFEyBM1kO+dRJDAHiLTNuR+7d8xUtMA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAPv94P3ftsjUHSHc+soG1y6a7XO48s0SuX+cf1MmWX1Q9LRw2juR4f2kKgqPZpoRDLvnpFZwywjg2mJBoenBqAlUjpPC8P61lvBHbIBaAgr8WY6OvG10rwol273OMpaiHhQTQwhzRSszTfhzKTs5QBdB3jWizEJbg/4q8i+zq0YdY9akfFCHU3WqxUBeOTG3rajL9WTHggMQ+hvi8ARxptutqTizKKNiCEhyTJpc/iNemKgiAllzoP+f/i1WWRdMWmGyc9hi0eCLZgmQ0GikbJbGIjcS045AbmtqincScZbhlJyFusJa2gA+B8xVt7tz6C7sya2NtMB7ckMjHJGl+cQQ1pqeGx/2xcuYlWwzCrzrOifIbtGCXLeN2hi0DEyR9wCLilMYGQ03jccCMkZ8gvRnlVQsdCkIqA6QQtbc4eA9cghTCoh1uJ+c96tqBnx4+X/aGiPIjTn9q3ERRu8mtfH+n8tnkm9N0nNCSbmyIAFmYLQbvSU+xPKdfIwxgF9Wg5CMRFbVdox3malqV7op5O6O1vOhpOKtUI7J8USkdTONBlIqNvtdGTlbA9zB6myLq/HSX9JWCgBbKGQk0Uix3d/pF857To3FWDxPUAmlErQPCbJs0WHxclPgKwl3DV0h5mlyUbMJjDEasHJ8y6aSaBVkvwvPpzJ7f/OAojKSpwP0=\",\"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+PzYgDppBlP0TUNN2epPYpv38cXZhZuV0yWcZPbFG8tr9ZoylEz8SCaRw2eECd/gUxOO/vmINMEqtNI/b6OdYC1NLtcNtg/DCAc5MK/jYkjCPQya4/YqIKeCUzadW1Q3E1YxYHrBA14ADFkU2ifXMaOP04/t3UdlENv0Fk7vTsLF3cV9fNagkuakNUeGyk6HLZnrmoBcBOmCTIiEpg3ch5RWaf7GECfu2/9D1ghb4kk5oT3+rBWOAT3IZjnliic7WafGCKYZ7Ylxv8IBHAwaXSYvrJEB79HYoRxIXfh5YT0Q/N9/isOtEhQ10berWB4wsQ/pCut5H6boYDv11CIrZAgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAAv33ljSlWsmtBKNAvoLwh2llzRtEdA9ZO2XsrCt+GAHwQeKS6ncHl/s11UWyX0NG1J0r0CrrPtLuY/9jHrHPpR1qcSPvOPUq8cQ0A6xZeF/7/a0l6XF9mwbAoyuVW6MNFPrWUYB2CZBjnc3dMoBAFAcIRMrGcp4VWsNcpklb9JAsOE4DPcGhROr09tEkpDciocH36PraK0bASgckM8yd2bHSmvRk91UNqzE2J4hojBb59M66mDAoRv1AE3fJNfn9AOf6XRvtd/JQ3/ozh50MrO881viaGPZjQ0KThnfrSlbg9VP8IUWcGwz5uXkrLibAb8hkpgmiozkgsvldLYiUzw=\",\"\",\"2020-07-30 14:55:52\",\"2020-07-30 14:55:52\",\"2021-07-30 14:55:52\",1596120952,1627656952,4,\"SkcFonuJQH7p\",5,\"\",\"\",\"2020-07-31 14:55:44\",\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}]\",\"QMSCUs0MH1j1\",\"[\"\"mailto:grindsa3@bar.local\"\"]\",\"2020-07-30 14:52:20\",\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",\"ES256\"\n2,\"UP5L6rCiLsQS\",615907363189272855,\"MIIEjjCCAnagAwIBAgIICIwke81xsRcwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU2MzVaFw0yMTA3MzAxNDU2MzVaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvj82IA6aQZT9E1DTdnqT2Kb9/HF2YWbldMlnGT2xRvLa/WaMpRM/EgmkcNnhAnf4FMTjv75iDTBKrTSP2+jnWAtTS7XDbYPwwgHOTCv42JIwj0MmuP2KiCnglM2nVtUNxNWMWB6wQNeAAxZFNon1zGjj9OP7d1HZRDb9BZO707Cxd3FfXzWoJLmpDVHhspOhy2Z65qAXATpgkyIhKYN3IeUVmn+xhAn7tv/Q9YIW+JJOaE9/qwVjgE9yGY55YonO1mnxgimGe2Jcb/CARwMGl0mL6yRAe/R2KEcSF34eWE9EPzff4rDrRIUNdG3q1geMLEP6QrreR+m6GA79dQiK2QIDAQABo4HIMIHFMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwgg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWyCDmZvbzMuYmFyLmxvY2FsMB0GA1UdDgQWBBRMgTNZDvnUSQwB4i0zbkfu3fMVLTAOBgNVHQ8BAf8EBAMCBaAwHwYDVR0jBBgwFoAUv96OjgYiIqutQ8jd1E+oq0hBPtUwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAFJHwJsrFtd/nF/+dd+6es2c6aCwBbMHDxUm3JZtz1Vc1hgqFkXhFrTBTMGHDyCzg5GgVQf/+eH8vK3SUWpPQ4sd078973MteCfYwbMKirLqN3LxwZd1Yt2ljnHvArTCExVIvK62Oc2uYLiDqTNOJt2ymui45Ifi0ctpcPIoSVxz7AFtnX38tMyVsEDw74m8P8W0GS2UMVep1yF3a53rnw0wQIZeiok+oBNZJhBf1jyUMypGHW8N4rLa/4ON37AR/pHbTvXvgnpY/IBNpWVezXQwe54YV2XYPdxOnptitLeQrA4RoyV17NLgQNYFMsXS3OX8VjoG0+Q09gT/L2g2A/3S9l4Penn5ox8rmkeqTQ1igRnzdgQfzA0IsJTjCMUrp5mys3iB258+2WFObvZ7WfIsTcXIrqKE2oQfUJtf1L4eF3FuSQp49L6CqaxjlfPrHjDXfyPKJdvIlIN3K/Fg4MrkgczF5MP6EWVufXnug6q/RvBwhxkDCLSW5IrD4+FPmiHKqvl+k5n57TT5fDexMoPfd7o/MpxkLLQPZZWSPnqkTFCsjE8tP0R0oyiUECBQDCwtX6FH116d3PQXl+ERTjYTI6jzhwD+gxvyFfPF2pykpaIkltJQxzk4ZCzYbpNHmYqrbo5XXlVo95TRqn3PpnwDth9qVGEEnycLhHEOCUDy\",\"MIICtzCCAZ8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+PzYgDppBlP0TUNN2epPYpv38cXZhZuV0yWcZPbFG8tr9ZoylEz8SCaRw2eECd/gUxOO/vmINMEqtNI/b6OdYC1NLtcNtg/DCAc5MK/jYkjCPQya4/YqIKeCUzadW1Q3E1YxYHrBA14ADFkU2ifXMaOP04/t3UdlENv0Fk7vTsLF3cV9fNagkuakNUeGyk6HLZnrmoBcBOmCTIiEpg3ch5RWaf7GECfu2/9D1ghb4kk5oT3+rBWOAT3IZjnliic7WafGCKYZ7Ylxv8IBHAwaXSYvrJEB79HYoRxIXfh5YT0Q/N9/isOtEhQ10berWB4wsQ/pCut5H6boYDv11CIrZAgMBAAGgWTBXBgkqhkiG9w0BCQ4xSjBIMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwgg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWyCDmZvbzMuYmFyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQB5V36iaXweC62qSDj7zTq8y6quHTOHlOSy1lRHwXkzy/IjPjotWVeDKLj/p/6dQ0+fbC/jfSCn3LARgDs/FPPGVYQNg2vMc+m5XGXz5D3orrt19Qm1rizREntreBlDJB3Gplqb+w+k9thXFi/XZcdJm/csEIMAXkmCZtjxWlqjNIlVNxLBOK4tHbKhrGpMVkPoKnSgrVzogSuQO5FcWT77tC9m/9kqRnnpMYn2iTbaygBxz//FHxiqBznmyRxzWyuRcCigaf7s1Z1hi8dEExauHTc2sRbo1g929rApr5Ch4GYeNMHfS/omfOYgaj3s96+8A66Z6s20CLv1HcVmAyit\",\"\",\"2020-07-30 14:56:35\",\"2020-07-30 14:56:35\",\"2021-07-30 14:56:35\",1596120995,1627656995,5,\"tDXY56holtPf\",5,\"\",\"\",\"2020-07-31 14:56:22\",\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo2.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo3.bar.local\"\"}]\",\"QMSCUs0MH1j1\",\"[\"\"mailto:grindsa3@bar.local\"\"]\",\"2020-07-30 14:52:20\",\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",\"ES256\"\n3,\"tMecnVgPwIe2\",2523585692901432379,\"MIIEfjCCAmagAwIBAgIIIwWT0TGl3DswDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU3MzNaFw0yMTA3MzAxNDU3MzNaMBkxFzAVBgNVBAMMDmZvbzQuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzlymrtLN0o8p6BX7wbEd7xtrDuLbxt97ExGZtE4LY+FoT0OeZayYuAbLaJRacwufPVfmjsAfUQQF7rk3VwXrFCQ8P1+Fgp3b76gNx337lO6c8HgLico+5eORj2Wz9erG+midWxinHp9V8l9HdiYGumJqrXx1vyrHKqLftMOnqsBl1Kep3fLm1+y1CEBGiwOhlPMcP4FZwZO8ARtqtznBooXJZlCs9skMh+mFsFQwJMtsso5SptVe4pjYdhzxEKRxElEQdH2G9Fwuz8RngebHVByjaCmFNwVNdUpsnwC97ZUNrw4tGE4sUGVzjmzhDTKBfQXnREPfx+DNzoiCcKygnwIDAQABo4G4MIG1MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb280LmJhci5sb2NhbIIOZm9vNS5iYXIubG9jYWwwHQYDVR0OBBYEFEhRSX17MufrAr+NjZlIxGtnnlw0MA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAJL3AWUPnWVU1vsD/AN7GQHSKPS7z2mgUyBMKz2VJTzrcA1MMnXcmEhNEq3t1PLPAOsqocKp4jnea3KJRbvQYRxXXVZuedH3VNYfGfp0G0PztBFOlX9Ib+M5uEqr4V3vZ1OXoCpeN1NSEmUUF00bHMC1qce9u0qxy+Lt3GGIF6UMrmQo8zl70m82F41EUbUmOHSsVMaN6eEs5BmWTyRerhlAXNuEgjxYxIiG3ZymChkpXSFJxb3AlAZ2Tj7OQuixuE0fAoT7V23CdOeW6lJfX/54IuDP3Kcn+bzfLesZKdKOSpMHAdi8Yayy6cGuk2cF4niy17ybbIU3Y5yWJD16HsrlOyBJUifT7DXefluM0rDiICm2Q4L+Qpk4D9gpxKyuZyyS77OT783QxiobujU5CF6DPFkuCiZeA1cPQw2RsCPdh24tzgGKoWlXKLrvqm0Fq+EQPUsq/u+Nb1M35P9ebFk1iT1pyiF0LP8SRnAbCJpnES/4YLOBbRCVhSincde0G3R38K8CkT6HH6RuXq8mtuF0l3Yuv4xaHQtFnZw6iY3xYRbGkNLb86orxaXpeR2s0CQxUvklqR4I0tyPA66YPxqyk4fuH6iLUGDgSedh+UL/SCtDXQP8e7Z8nELLIovoC2qrs3WcaO6GRjKnhLuJMS9SdLNw5wdgf1TBXdRMbcOI=\",\"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vNC5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOXKau0s3SjynoFfvBsR3vG2sO4tvG33sTEZm0Tgtj4WhPQ55lrJi4BstolFpzC589V+aOwB9RBAXuuTdXBesUJDw/X4WCndvvqA3HffuU7pzweAuJyj7l45GPZbP16sb6aJ1bGKcen1XyX0d2Jga6YmqtfHW/Kscqot+0w6eqwGXUp6nd8ubX7LUIQEaLA6GU8xw/gVnBk7wBG2q3OcGihclmUKz2yQyH6YWwVDAky2yyjlKm1V7imNh2HPEQpHESURB0fYb0XC7PxGeB5sdUHKNoKYU3BU11SmyfAL3tlQ2vDi0YTixQZXOObOENMoF9BedEQ9/H4M3OiIJwrKCfAgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb280LmJhci5sb2NhbIIOZm9vNS5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAISpO633jLjWQ0rO02+GFhvHU7JI3ffIK1+k2w8qkb39Laa8BvX9GRXAKb2zYea1VX+2n6wbdFqDU2NtSKo7G3v71p4V+q8kGUM9clAjrd+o1xloincaz0q31SsOS4MfRhC//hUt01AFbQ8ha5D8B3bd3z7sGDCp0bHeNQ7NUqCT6cCiJGTgbhhZam9TagOQUXkkiUKVnbXG3LrD11E46HTnKimfyjGLJH9Vcajwkt2U4eJ4OICWiaO4lTjn34OkbvX8pekwSYTgZfqpMtHm4xwQTSfcT3WI66fdTpwJnWyUDhRS2NdOkfiv1/zJHQ5bv850bURLAjFxonGlV8vojBo=\",\"\",\"2020-07-30 14:57:33\",\"2020-07-30 14:57:33\",\"2021-07-30 14:57:33\",1596121053,1627657053,6,\"pMqVtHqIkT64\",5,\"\",\"\",\"2020-07-31 14:57:24\",\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo4.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"foo5.bar.local\"\"}]\",\"QMSCUs0MH1j1\",\"[\"\"mailto:grindsa3@bar.local\"\"]\",\"2020-07-30 14:52:20\",\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",\"ES256\"\n4,\"whYmXoDhgvqz\",3267690953728815010,\"MIIEgDCCAmigAwIBAgIILVkrnATDb6IwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU4NTJaFw0yMTA3MzAxNDU4NTJaMB4xHDAaBgNVBAMME2NlcnRib3QtMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDH8FbkzIW/JdlLZIEvFWYxYBpTK1j0VmACv1/RNf+fxGvXBMyi0oUo5lOojBjiYLwBDnPCa1UJs3jiO71oGScZeQvi7IYdymoXFaC1AgXt4xjVHe57HNPoxg289MP+nxt46A1rQHQS4hjc0X2xVNKC03koCWgAmTHC44DE/lZAZhUY/2RMEVM5qKPaGTPVg2pH/aTFtlS66mcln8rcVZiuMQE52iDkTTwAf9+c304znND5zKaLEgetpbMIpgrag4FzU5Lin5cgoyHKUDIF3tYrDrg5H2qc+BZiSWo02XhW8wzcuzBWn7zszk0xbje21KyaMiCqm56bNwNRCJBdyaG9AgMBAAGjgbUwgbIwMwYDVR0RBCwwKoITY2VydGJvdC0xLmJhci5sb2NhbIITY2VydGJvdC0yLmJhci5sb2NhbDAdBgNVHQ4EFgQUb9gfzHAct6ZnYUZHPhJSRpNUVRwwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBB+PgEsr7YSSPcHg4EIjb7Frltjk6V+iqST4GVY92G5LdA3Nh7twNj1VF16eETVZIO8nuWyF4mTqTWnRElVoynqRf/JqMwsR+Ym7ahv1gZKqqeTj9s1lQcHsNCqrB66dnO6HeE9//agJiKkyNFVNJQQ2MZC7G6dEIV7dkHg+L5iakDA0EkKmbXFyGVABkhex0lkctjVvkiZ89OupXMcNDX93nFABp4Lm0AusVocyQf/ff2BK02UrADhInBVRF5sXVuTC5WeNJaUgz9ECgmoUTa4/zxLlufQZURc1wUDwGYyFWjKIkGsQBgrQOH12gIqrZzA9vMZMhhgJvWuQtQdvw8lpHmOhY3041kNibKwt52QqULIV//S6ErQwrSrbSBS3HN6XzloWXm4Lor9vesrUvi90yH/cylwh+ALiORC9oAdatGV1pQUu6a1qY8tBLEU5/QyKj5lZ8sbr0+uYpQIOqv918jURXt0+neGtjBN4HtEHBcQio5ndjnAvuHSj2lFyhjjVSdlsF8KCF4JwmO6Oi+qGq5jOuwWi1a1n2brfmbnbuIj9uq21U716nsk3ICUHEpSLpRjsEVZFV/1BF3mtxUpNVp7I5qh0gt5HkHaJxQ1AY8Xod8o7tFyEgDobhFQx02pXwynAqgbSfkCtcHq5+/ilH4Z/QtetACELY9/yxgwQ==\",\"MIICizCCAXMCAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMfwVuTMhb8l2UtkgS8VZjFgGlMrWPRWYAK/X9E1/5/Ea9cEzKLShSjmU6iMGOJgvAEOc8JrVQmzeOI7vWgZJxl5C+Lshh3KahcVoLUCBe3jGNUd7nsc0+jGDbz0w/6fG3joDWtAdBLiGNzRfbFU0oLTeSgJaACZMcLjgMT+VkBmFRj/ZEwRUzmoo9oZM9WDakf9pMW2VLrqZyWfytxVmK4xATnaIORNPAB/35zfTjOc0PnMposSB62lswimCtqDgXNTkuKflyCjIcpQMgXe1isOuDkfapz4FmJJajTZeFbzDNy7MFafvOzOTTFuN7bUrJoyIKqbnps3A1EIkF3Job0CAwEAAaBGMEQGCSqGSIb3DQEJDjE3MDUwMwYDVR0RBCwwKoITY2VydGJvdC0xLmJhci5sb2NhbIITY2VydGJvdC0yLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEANMlw7E1eLSaQwKMqvHKUcmaDDRNSa3VG6ekSCyVObVsVBg86hmkYhVmMtrfStCvM6O/wC1eAZez25TSeOcXd3Sxy3ScJLsgo9/wxNMnSngHFQcIU96EBdJeNYsMRkz0b0r2qA3FJKm5tpSCj5qZH+Cgm0y4Qwr0oD1LjMwHG0breqdizNxZGqGaCbNEWsHom6N2WU69xB5EQw+TSD9xoyL+WdvjJM6CM6EFlCFcwGu6iC6ypDJ4c17Ku+Zh7czs1XZEnW+sdgflvDxaPYp9092A947mSDXLGKJQZOk5viRd7h24MbgjPrzg3DLhvNZd+Of8caOQliZ+b1MU7IdVclw==\",\"\",\"2020-07-30 14:58:52\",\"2020-07-30 14:58:52\",\"2021-07-30 14:58:52\",1596121132,1627657132,7,\"bZU4xme1P11D\",5,\"\",\"\",\"2020-07-31 14:58:49\",\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"certbot-2.bar.local\"\"}]\",\"brykxxUN24fZ\",\"[\"\"mailto:certbot@bar.local\"\"]\",\"2020-07-30 14:58:26\",\"{\"\"e\"\": \"\"AQAB\"\", \"\"kty\"\": \"\"RSA\"\", \"\"n\"\": \"\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\"\"}\",\"RS256\"\n5,\"Qnq7VE1wudyJ\",1325441394109717098,\"MIIEkjCCAnqgAwIBAgIIEmTp7OqCGmowDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNTAyMTNaFw0yMTA3MzAxNTAyMTNaMB4xHDAaBgNVBAMME2NlcnQuc3Rpci5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL7fFJ/qLoQQyiYTJhxCJRSFgdPPCr0mgBbQB6MUEFLu5MXjvosDM7cCX4YxTRZEVvevZcW/6jQsac0gb2cNWvJ9qtbuxMa8pjUw5kOy17ETzS1DTJf8EiP+xGUJzRPYFZRp0cBiJ58gDWyx6r1cC/7bDx96vaNF8QS8pEwG3Wl3e9eWgdB+49vNDLHuSlGo6qYwK5dkLTt5s8c4UvyefnXdFowZjiPYTUZZqwPqViIgkqtVbYpMNCVpgn5EIcstCvsQ3yRv+cjjbGPCAwqH2hrepl875QqqHdsCXm0fK1nPTCBxyMXR5cE7Ox+0B5PWac04jJDTa+BjslgkYHpeIHAgMBAAGjgccwgcQwCwYDVR0PBAQDAgXgMB4GA1UdEQQXMBWCE2NlcnQuc3Rpci5iYXIubG9jYWwwGAYIKwYBBQUHARoEDDAKoAgWBjEyMzQ1NjAdBgNVHQ4EFgQU4purbPFsQT7or7UxGmvCpoPe0gYwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQCy3VVi162R86K9Zs1qNPeHxyBmkyQINHcbxzKqzRT56FZD6n/Y/1yf3Ba/PQNUul/8KJVvOWJcdI1BoXxa+M0A7+0iddlvZndwq4kW664O+n4pdeuc5c3N2sMysrD4ojZ9DqEANuAqR8/yktdkIP3lArUX8rYjWbvDpqFjaPY1JDKsOcexRvmNpxFU4HoQFPgRHb4OWUtHa0DBbW0IM9EiPOK5HXYniPdHFapIno9r6hUwGkKbeKE2hpwKMjZuvv+2h8qSq+qtxQUs9PDe5vmSXBcqaKYZtVTz5p/Yb8pAUUfh3V5XAojHd348+UFZySPJgiBN5VJYGBRCBHAHIhUgv4sE0NjvCGE2X2MWf0mBJJ9+qL3rH4J2gCuW2nvZv+p/Vmv5PgEQCVXjj/bwJX7EzISThJHZ44NA0ny1c0ujCCzK7E0Qsko+8ySDiq+lONRKsq7wy9vW0h2lP5N9y7fMQf2S7t/+BWE6Hmt9NiGZnPRQJwh///yj92uc6A/n0sl1i5pMlLouauGmmmZC+z0lbFSR6HVu+yNSTBd1i3Xlz0cihXIqnkzcbFZbaYaUzSavyUxaw6l8RxLr1A4rOjdiNkswcY8CyGpTLox+WrviNwpSu0xl5dIRLQX9AmI6sEm24dw+MN8J0F95xQZD2XRMWi4l+GjbBVcG6YEKIpbYGQ==\",\"MIICuzCCAaMCAQAwHjEcMBoGA1UEAwwTY2VydC5zdGlyLmJhci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMvt8Un+ouhBDKJhMmHEIlFIWB088KvSaAFtAHoxQQUu7kxeO+iwMztwJfhjFNFkRW969lxb/qNCxpzSBvZw1a8n2q1u7ExrymNTDmQ7LXsRPNLUNMl/wSI/7EZQnNE9gVlGnRwGInnyANbLHqvVwL/tsPH3q9o0XxBLykTAbdaXd715aB0H7j280Mse5KUajqpjArl2QtO3mzxzhS/J5+dd0WjBmOI9hNRlmrA+pWIiCSq1Vtikw0JWmCfkQhyy0K+xDfJG/5yONsY8IDCofaGt6mXzvlCqod2wJebR8rWc9MIHHIxdHlwTs7H7QHk9ZpzTiMkNNr4GOyWCRgel4gcCAwEAAaBYMFYGCSqGSIb3DQEJDjFJMEcwCwYDVR0PBAQDAgXgMB4GA1UdEQQXMBWCE2NlcnQuc3Rpci5iYXIubG9jYWwwGAYIKwYBBQUHARoEDDAKoAgWBjEyMzQ1NjANBgkqhkiG9w0BAQsFAAOCAQEAkUE5X1/Vx2QXlyTQ/QLyKxw18082uXV1L8i9Zr1GgtzOY2Qs4oFXSICuGQUaVVv7NihtQ4I/6b7TEgDXTUUffhlrQZ/lcTMIWrDP7+aL2nLU/Xhn2wC+wCK9eWpbZwAtBbK6khT8t95KO4K7Mfh/RKzDsXsFmx0q02iJ0KYt52tNsb9n6yAlZnCcJzAcD66nSMpi4R2/8pmMzw8toNrot5dENumMrH36RvtXArmhKQpYbtHN5iXWd59W9EA2LG7AUZHtS5Z6oklckzPBUKaetNuoeC4u3pTk3o6bbD6qoQagTHBBc4VJkc/SzgAoeumCs0PIbUNgVqtq42gtqHTbhQ==\",\"\",\"2020-07-30 15:02:13\",\"2020-07-30 15:02:13\",\"2021-07-30 15:02:13\",1596121333,1627657333,8,\"BbYOopY9ged0\",5,\"\",\"\",\"2020-07-31 15:02:09\",\"[{\"\"type\"\": \"\"TNAuthList\"\", \"\"value\"\": \"\"MAqgCBYGMTIzNDU2\"\"}]\",\"QMSCUs0MH1j1\",\"[\"\"mailto:grindsa3@bar.local\"\"]\",\"2020-07-30 14:52:20\",\"{\"\"crv\"\": \"\"P-256\"\", \"\"kty\"\": \"\"EC\"\", \"\"x\"\": \"\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\"\", \"\"y\"\": \"\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\"\"}\",\"ES256\"\n6,\"KQ10vV5vN0ja\",552334702840161063,\"MIIDyTCCAbGgAwIBAgIIB6pJfF/ooycwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNTAzNTdaFw0yMTA3MzAxNTAzNTdaMBsxGTAXBgNVBAMTEGxlZ28tMS5iYXIubG9jYWwwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAS/l9y8W6W6zIAKrRYY1UOxB+xW/JpHcqLAk2B4PjDaZNT66IvD9U417Mh0RMdGpUbivp/QQv6fKeVOoh/GqagQt1dAIaSBl1jlT9eTeVHp9XmN14Fn5JcWPwJsYBHCSN2jga8wgawwLQYDVR0RBCYwJIIQbGVnby0xLmJhci5sb2NhbIIQbGVnby0yLmJhci5sb2NhbDAdBgNVHQ4EFgQUHFAp2Lkf29VqzuRqOOUX2LwALdcwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBn6/KOHlorWP9AClchORnqINknQ4+sVAP7sub6JoKv8aTJSGApIr+vd2dY8Yey/nfVWpVr5q2uRRU1gahFN5/Fjbk92Ll/lNIdHZGyX18ZF7fQSYzwdKqUSqOtLgKYqqWsCrWHFWNKmhC6GEhj6QXAOhDzwXnbxrPvIhgfC3ZFXm5LOLJexdFzC8qqUl2OCSKOFPywvCzJBbha3E3N0Snpxf1vWNdCNSuf3AlQzFCm0GMkBfpLbEQE2ELr3bhBrzVVtmAF/28Km6HvN2TUyF3VlLWHspaXsTuHrbFfY+rFtBIMTha1vnUMGuZio9acj5HOY2AqxdvC1xQ7inrHz6PMyHX7xtW1PoiI6qFU5GyluC7bS+MNhphJ8nv3sa4eQIubLUgmVFCLHf3qIpgQJjkxXQZOb5nnq0J4wISWwrjqYyZbM9f1/tTJ3YbeD4odjajW+j8Vf9aE0vlxHYLjBmpS185hew46M2foExaDhHI3TDfox9hE5Mln4BiWPGSm23I6TXJPv4LmBFHI3bimgt4zEdNcCEPkOjqvQM/hw9Fdva2ZACpEsOykE1NWSNywSriHT2Kzv9xeYs/uWzzu73xKXw3VpbrtO6GW9u+fca8+O8ioIZCZiHQlKvrPNWPll+yeaMiCG4vcdOy2RwqDywu1C9qW0BjTTZQh9u+quUiGWg==\",\"MIIBVDCB2gIBADAbMRkwFwYDVQQDExBsZWdvLTEuYmFyLmxvY2FsMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEv5fcvFulusyACq0WGNVDsQfsVvyaR3KiwJNgeD4w2mTU+uiLw/VONezIdETHRqVG4r6f0EL+nynlTqIfxqmoELdXQCGkgZdY5U/Xk3lR6fV5jdeBZ+SXFj8CbGARwkjdoEAwPgYJKoZIhvcNAQkOMTEwLzAtBgNVHREEJjAkghBsZWdvLTEuYmFyLmxvY2FsghBsZWdvLTIuYmFyLmxvY2FsMAoGCCqGSM49BAMDA2kAMGYCMQD1Ge3x08RJgw5lgvQrQy2tfxb1TU2qUNfCUJuWI+TVFRYf7HrvvEwVYHlzyq/thoUCMQDwtnBIO6RTwS0SfYI9pZbEAgtlSCjdTZC3ZAXyzleJRaytx6JoZCrG8PKIjxyRDGU=\",\"\",\"2020-07-30 15:03:57\",\"2020-07-30 15:03:57\",\"2021-07-30 15:03:57\",1596121437,1627657437,9,\"AscxIwNhKwpm\",5,\"\",\"\",\"2020-07-31 15:03:56\",\"[{\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-1.bar.local\"\"}, {\"\"type\"\": \"\"dns\"\", \"\"value\"\": \"\"lego-2.bar.local\"\"}]\",\"Z9cKiTD0n3No\",\"[\"\"mailto:lego@bar.local\"\"]\",\"2020-07-30 15:03:56\",\"{\"\"kty\"\": \"\"EC\"\", \"\"crv\"\": \"\"P-384\"\", \"\"x\"\": \"\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\"\", \"\"y\"\": \"\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\"\"}\",\"ES384\"\n"
  },
  {
    "path": "examples/reports/cert_report.json",
    "content": "[\n    {\n        \"certificate.id\": 1,\n        \"certificate.name\": \"i0hhZkP34dKf\",\n        \"certificate.cert_raw\": \"MIIEfjCCAmagAwIBAgIIPQh1QcnhjOAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU1NTJaFw0yMTA3MzAxNDU1NTJaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvj82IA6aQZT9E1DTdnqT2Kb9/HF2YWbldMlnGT2xRvLa/WaMpRM/EgmkcNnhAnf4FMTjv75iDTBKrTSP2+jnWAtTS7XDbYPwwgHOTCv42JIwj0MmuP2KiCnglM2nVtUNxNWMWB6wQNeAAxZFNon1zGjj9OP7d1HZRDb9BZO707Cxd3FfXzWoJLmpDVHhspOhy2Z65qAXATpgkyIhKYN3IeUVmn+xhAn7tv/Q9YIW+JJOaE9/qwVjgE9yGY55YonO1mnxgimGe2Jcb/CARwMGl0mL6yRAe/R2KEcSF34eWE9EPzff4rDrRIUNdG3q1geMLEP6QrreR+m6GA79dQiK2QIDAQABo4G4MIG1MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwHQYDVR0OBBYEFEyBM1kO+dRJDAHiLTNuR+7d8xUtMA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAPv94P3ftsjUHSHc+soG1y6a7XO48s0SuX+cf1MmWX1Q9LRw2juR4f2kKgqPZpoRDLvnpFZwywjg2mJBoenBqAlUjpPC8P61lvBHbIBaAgr8WY6OvG10rwol273OMpaiHhQTQwhzRSszTfhzKTs5QBdB3jWizEJbg/4q8i+zq0YdY9akfFCHU3WqxUBeOTG3rajL9WTHggMQ+hvi8ARxptutqTizKKNiCEhyTJpc/iNemKgiAllzoP+f/i1WWRdMWmGyc9hi0eCLZgmQ0GikbJbGIjcS045AbmtqincScZbhlJyFusJa2gA+B8xVt7tz6C7sya2NtMB7ckMjHJGl+cQQ1pqeGx/2xcuYlWwzCrzrOifIbtGCXLeN2hi0DEyR9wCLilMYGQ03jccCMkZ8gvRnlVQsdCkIqA6QQtbc4eA9cghTCoh1uJ+c96tqBnx4+X/aGiPIjTn9q3ERRu8mtfH+n8tnkm9N0nNCSbmyIAFmYLQbvSU+xPKdfIwxgF9Wg5CMRFbVdox3malqV7op5O6O1vOhpOKtUI7J8USkdTONBlIqNvtdGTlbA9zB6myLq/HSX9JWCgBbKGQk0Uix3d/pF857To3FWDxPUAmlErQPCbJs0WHxclPgKwl3DV0h5mlyUbMJjDEasHJ8y6aSaBVkvwvPpzJ7f/OAojKSpwP0=\",\n        \"certificate.csr\": \"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+PzYgDppBlP0TUNN2epPYpv38cXZhZuV0yWcZPbFG8tr9ZoylEz8SCaRw2eECd/gUxOO/vmINMEqtNI/b6OdYC1NLtcNtg/DCAc5MK/jYkjCPQya4/YqIKeCUzadW1Q3E1YxYHrBA14ADFkU2ifXMaOP04/t3UdlENv0Fk7vTsLF3cV9fNagkuakNUeGyk6HLZnrmoBcBOmCTIiEpg3ch5RWaf7GECfu2/9D1ghb4kk5oT3+rBWOAT3IZjnliic7WafGCKYZ7Ylxv8IBHAwaXSYvrJEB79HYoRxIXfh5YT0Q/N9/isOtEhQ10berWB4wsQ/pCut5H6boYDv11CIrZAgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAAv33ljSlWsmtBKNAvoLwh2llzRtEdA9ZO2XsrCt+GAHwQeKS6ncHl/s11UWyX0NG1J0r0CrrPtLuY/9jHrHPpR1qcSPvOPUq8cQ0A6xZeF/7/a0l6XF9mwbAoyuVW6MNFPrWUYB2CZBjnc3dMoBAFAcIRMrGcp4VWsNcpklb9JAsOE4DPcGhROr09tEkpDciocH36PraK0bASgckM8yd2bHSmvRk91UNqzE2J4hojBb59M66mDAoRv1AE3fJNfn9AOf6XRvtd/JQ3/ozh50MrO881viaGPZjQ0KThnfrSlbg9VP8IUWcGwz5uXkrLibAb8hkpgmiozkgsvldLYiUzw=\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 14:55:52\",\n        \"certificate.issue_uts\": 1596120952,\n        \"certificate.expire_uts\": 1627656952,\n        \"order.id\": 4,\n        \"order.name\": \"SkcFonuJQH7p\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 14:55:44\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}]\",\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"certificate.issue_date\": \"2020-07-30 14:55:52\",\n        \"certificate.expire_date\": \"2021-07-30 14:55:52\",\n        \"certificate.serial\": 4397893961547615456\n    },\n    {\n        \"certificate.id\": 2,\n        \"certificate.name\": \"UP5L6rCiLsQS\",\n        \"certificate.cert_raw\": \"MIIEjjCCAnagAwIBAgIICIwke81xsRcwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU2MzVaFw0yMTA3MzAxNDU2MzVaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvj82IA6aQZT9E1DTdnqT2Kb9/HF2YWbldMlnGT2xRvLa/WaMpRM/EgmkcNnhAnf4FMTjv75iDTBKrTSP2+jnWAtTS7XDbYPwwgHOTCv42JIwj0MmuP2KiCnglM2nVtUNxNWMWB6wQNeAAxZFNon1zGjj9OP7d1HZRDb9BZO707Cxd3FfXzWoJLmpDVHhspOhy2Z65qAXATpgkyIhKYN3IeUVmn+xhAn7tv/Q9YIW+JJOaE9/qwVjgE9yGY55YonO1mnxgimGe2Jcb/CARwMGl0mL6yRAe/R2KEcSF34eWE9EPzff4rDrRIUNdG3q1geMLEP6QrreR+m6GA79dQiK2QIDAQABo4HIMIHFMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwgg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWyCDmZvbzMuYmFyLmxvY2FsMB0GA1UdDgQWBBRMgTNZDvnUSQwB4i0zbkfu3fMVLTAOBgNVHQ8BAf8EBAMCBaAwHwYDVR0jBBgwFoAUv96OjgYiIqutQ8jd1E+oq0hBPtUwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAFJHwJsrFtd/nF/+dd+6es2c6aCwBbMHDxUm3JZtz1Vc1hgqFkXhFrTBTMGHDyCzg5GgVQf/+eH8vK3SUWpPQ4sd078973MteCfYwbMKirLqN3LxwZd1Yt2ljnHvArTCExVIvK62Oc2uYLiDqTNOJt2ymui45Ifi0ctpcPIoSVxz7AFtnX38tMyVsEDw74m8P8W0GS2UMVep1yF3a53rnw0wQIZeiok+oBNZJhBf1jyUMypGHW8N4rLa/4ON37AR/pHbTvXvgnpY/IBNpWVezXQwe54YV2XYPdxOnptitLeQrA4RoyV17NLgQNYFMsXS3OX8VjoG0+Q09gT/L2g2A/3S9l4Penn5ox8rmkeqTQ1igRnzdgQfzA0IsJTjCMUrp5mys3iB258+2WFObvZ7WfIsTcXIrqKE2oQfUJtf1L4eF3FuSQp49L6CqaxjlfPrHjDXfyPKJdvIlIN3K/Fg4MrkgczF5MP6EWVufXnug6q/RvBwhxkDCLSW5IrD4+FPmiHKqvl+k5n57TT5fDexMoPfd7o/MpxkLLQPZZWSPnqkTFCsjE8tP0R0oyiUECBQDCwtX6FH116d3PQXl+ERTjYTI6jzhwD+gxvyFfPF2pykpaIkltJQxzk4ZCzYbpNHmYqrbo5XXlVo95TRqn3PpnwDth9qVGEEnycLhHEOCUDy\",\n        \"certificate.csr\": \"MIICtzCCAZ8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+PzYgDppBlP0TUNN2epPYpv38cXZhZuV0yWcZPbFG8tr9ZoylEz8SCaRw2eECd/gUxOO/vmINMEqtNI/b6OdYC1NLtcNtg/DCAc5MK/jYkjCPQya4/YqIKeCUzadW1Q3E1YxYHrBA14ADFkU2ifXMaOP04/t3UdlENv0Fk7vTsLF3cV9fNagkuakNUeGyk6HLZnrmoBcBOmCTIiEpg3ch5RWaf7GECfu2/9D1ghb4kk5oT3+rBWOAT3IZjnliic7WafGCKYZ7Ylxv8IBHAwaXSYvrJEB79HYoRxIXfh5YT0Q/N9/isOtEhQ10berWB4wsQ/pCut5H6boYDv11CIrZAgMBAAGgWTBXBgkqhkiG9w0BCQ4xSjBIMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwgg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWyCDmZvbzMuYmFyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQB5V36iaXweC62qSDj7zTq8y6quHTOHlOSy1lRHwXkzy/IjPjotWVeDKLj/p/6dQ0+fbC/jfSCn3LARgDs/FPPGVYQNg2vMc+m5XGXz5D3orrt19Qm1rizREntreBlDJB3Gplqb+w+k9thXFi/XZcdJm/csEIMAXkmCZtjxWlqjNIlVNxLBOK4tHbKhrGpMVkPoKnSgrVzogSuQO5FcWT77tC9m/9kqRnnpMYn2iTbaygBxz//FHxiqBznmyRxzWyuRcCigaf7s1Z1hi8dEExauHTc2sRbo1g929rApr5Ch4GYeNMHfS/omfOYgaj3s96+8A66Z6s20CLv1HcVmAyit\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 14:56:35\",\n        \"certificate.issue_uts\": 1596120995,\n        \"certificate.expire_uts\": 1627656995,\n        \"order.id\": 5,\n        \"order.name\": \"tDXY56holtPf\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 14:56:22\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo2.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo3.bar.local\\\"}]\",\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"certificate.issue_date\": \"2020-07-30 14:56:35\",\n        \"certificate.expire_date\": \"2021-07-30 14:56:35\",\n        \"certificate.serial\": 615907363189272855\n    },\n    {\n        \"certificate.id\": 3,\n        \"certificate.name\": \"tMecnVgPwIe2\",\n        \"certificate.cert_raw\": \"MIIEfjCCAmagAwIBAgIIIwWT0TGl3DswDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU3MzNaFw0yMTA3MzAxNDU3MzNaMBkxFzAVBgNVBAMMDmZvbzQuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzlymrtLN0o8p6BX7wbEd7xtrDuLbxt97ExGZtE4LY+FoT0OeZayYuAbLaJRacwufPVfmjsAfUQQF7rk3VwXrFCQ8P1+Fgp3b76gNx337lO6c8HgLico+5eORj2Wz9erG+midWxinHp9V8l9HdiYGumJqrXx1vyrHKqLftMOnqsBl1Kep3fLm1+y1CEBGiwOhlPMcP4FZwZO8ARtqtznBooXJZlCs9skMh+mFsFQwJMtsso5SptVe4pjYdhzxEKRxElEQdH2G9Fwuz8RngebHVByjaCmFNwVNdUpsnwC97ZUNrw4tGE4sUGVzjmzhDTKBfQXnREPfx+DNzoiCcKygnwIDAQABo4G4MIG1MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb280LmJhci5sb2NhbIIOZm9vNS5iYXIubG9jYWwwHQYDVR0OBBYEFEhRSX17MufrAr+NjZlIxGtnnlw0MA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAJL3AWUPnWVU1vsD/AN7GQHSKPS7z2mgUyBMKz2VJTzrcA1MMnXcmEhNEq3t1PLPAOsqocKp4jnea3KJRbvQYRxXXVZuedH3VNYfGfp0G0PztBFOlX9Ib+M5uEqr4V3vZ1OXoCpeN1NSEmUUF00bHMC1qce9u0qxy+Lt3GGIF6UMrmQo8zl70m82F41EUbUmOHSsVMaN6eEs5BmWTyRerhlAXNuEgjxYxIiG3ZymChkpXSFJxb3AlAZ2Tj7OQuixuE0fAoT7V23CdOeW6lJfX/54IuDP3Kcn+bzfLesZKdKOSpMHAdi8Yayy6cGuk2cF4niy17ybbIU3Y5yWJD16HsrlOyBJUifT7DXefluM0rDiICm2Q4L+Qpk4D9gpxKyuZyyS77OT783QxiobujU5CF6DPFkuCiZeA1cPQw2RsCPdh24tzgGKoWlXKLrvqm0Fq+EQPUsq/u+Nb1M35P9ebFk1iT1pyiF0LP8SRnAbCJpnES/4YLOBbRCVhSincde0G3R38K8CkT6HH6RuXq8mtuF0l3Yuv4xaHQtFnZw6iY3xYRbGkNLb86orxaXpeR2s0CQxUvklqR4I0tyPA66YPxqyk4fuH6iLUGDgSedh+UL/SCtDXQP8e7Z8nELLIovoC2qrs3WcaO6GRjKnhLuJMS9SdLNw5wdgf1TBXdRMbcOI=\",\n        \"certificate.csr\": \"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vNC5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOXKau0s3SjynoFfvBsR3vG2sO4tvG33sTEZm0Tgtj4WhPQ55lrJi4BstolFpzC589V+aOwB9RBAXuuTdXBesUJDw/X4WCndvvqA3HffuU7pzweAuJyj7l45GPZbP16sb6aJ1bGKcen1XyX0d2Jga6YmqtfHW/Kscqot+0w6eqwGXUp6nd8ubX7LUIQEaLA6GU8xw/gVnBk7wBG2q3OcGihclmUKz2yQyH6YWwVDAky2yyjlKm1V7imNh2HPEQpHESURB0fYb0XC7PxGeB5sdUHKNoKYU3BU11SmyfAL3tlQ2vDi0YTixQZXOObOENMoF9BedEQ9/H4M3OiIJwrKCfAgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb280LmJhci5sb2NhbIIOZm9vNS5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAISpO633jLjWQ0rO02+GFhvHU7JI3ffIK1+k2w8qkb39Laa8BvX9GRXAKb2zYea1VX+2n6wbdFqDU2NtSKo7G3v71p4V+q8kGUM9clAjrd+o1xloincaz0q31SsOS4MfRhC//hUt01AFbQ8ha5D8B3bd3z7sGDCp0bHeNQ7NUqCT6cCiJGTgbhhZam9TagOQUXkkiUKVnbXG3LrD11E46HTnKimfyjGLJH9Vcajwkt2U4eJ4OICWiaO4lTjn34OkbvX8pekwSYTgZfqpMtHm4xwQTSfcT3WI66fdTpwJnWyUDhRS2NdOkfiv1/zJHQ5bv850bURLAjFxonGlV8vojBo=\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 14:57:33\",\n        \"certificate.issue_uts\": 1596121053,\n        \"certificate.expire_uts\": 1627657053,\n        \"order.id\": 6,\n        \"order.name\": \"pMqVtHqIkT64\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 14:57:24\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo4.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"foo5.bar.local\\\"}]\",\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"certificate.issue_date\": \"2020-07-30 14:57:33\",\n        \"certificate.expire_date\": \"2021-07-30 14:57:33\",\n        \"certificate.serial\": 2523585692901432379\n    },\n    {\n        \"certificate.id\": 4,\n        \"certificate.name\": \"whYmXoDhgvqz\",\n        \"certificate.cert_raw\": \"MIIEgDCCAmigAwIBAgIILVkrnATDb6IwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNDU4NTJaFw0yMTA3MzAxNDU4NTJaMB4xHDAaBgNVBAMME2NlcnRib3QtMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDH8FbkzIW/JdlLZIEvFWYxYBpTK1j0VmACv1/RNf+fxGvXBMyi0oUo5lOojBjiYLwBDnPCa1UJs3jiO71oGScZeQvi7IYdymoXFaC1AgXt4xjVHe57HNPoxg289MP+nxt46A1rQHQS4hjc0X2xVNKC03koCWgAmTHC44DE/lZAZhUY/2RMEVM5qKPaGTPVg2pH/aTFtlS66mcln8rcVZiuMQE52iDkTTwAf9+c304znND5zKaLEgetpbMIpgrag4FzU5Lin5cgoyHKUDIF3tYrDrg5H2qc+BZiSWo02XhW8wzcuzBWn7zszk0xbje21KyaMiCqm56bNwNRCJBdyaG9AgMBAAGjgbUwgbIwMwYDVR0RBCwwKoITY2VydGJvdC0xLmJhci5sb2NhbIITY2VydGJvdC0yLmJhci5sb2NhbDAdBgNVHQ4EFgQUb9gfzHAct6ZnYUZHPhJSRpNUVRwwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBB+PgEsr7YSSPcHg4EIjb7Frltjk6V+iqST4GVY92G5LdA3Nh7twNj1VF16eETVZIO8nuWyF4mTqTWnRElVoynqRf/JqMwsR+Ym7ahv1gZKqqeTj9s1lQcHsNCqrB66dnO6HeE9//agJiKkyNFVNJQQ2MZC7G6dEIV7dkHg+L5iakDA0EkKmbXFyGVABkhex0lkctjVvkiZ89OupXMcNDX93nFABp4Lm0AusVocyQf/ff2BK02UrADhInBVRF5sXVuTC5WeNJaUgz9ECgmoUTa4/zxLlufQZURc1wUDwGYyFWjKIkGsQBgrQOH12gIqrZzA9vMZMhhgJvWuQtQdvw8lpHmOhY3041kNibKwt52QqULIV//S6ErQwrSrbSBS3HN6XzloWXm4Lor9vesrUvi90yH/cylwh+ALiORC9oAdatGV1pQUu6a1qY8tBLEU5/QyKj5lZ8sbr0+uYpQIOqv918jURXt0+neGtjBN4HtEHBcQio5ndjnAvuHSj2lFyhjjVSdlsF8KCF4JwmO6Oi+qGq5jOuwWi1a1n2brfmbnbuIj9uq21U716nsk3ICUHEpSLpRjsEVZFV/1BF3mtxUpNVp7I5qh0gt5HkHaJxQ1AY8Xod8o7tFyEgDobhFQx02pXwynAqgbSfkCtcHq5+/ilH4Z/QtetACELY9/yxgwQ==\",\n        \"certificate.csr\": \"MIICizCCAXMCAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMfwVuTMhb8l2UtkgS8VZjFgGlMrWPRWYAK/X9E1/5/Ea9cEzKLShSjmU6iMGOJgvAEOc8JrVQmzeOI7vWgZJxl5C+Lshh3KahcVoLUCBe3jGNUd7nsc0+jGDbz0w/6fG3joDWtAdBLiGNzRfbFU0oLTeSgJaACZMcLjgMT+VkBmFRj/ZEwRUzmoo9oZM9WDakf9pMW2VLrqZyWfytxVmK4xATnaIORNPAB/35zfTjOc0PnMposSB62lswimCtqDgXNTkuKflyCjIcpQMgXe1isOuDkfapz4FmJJajTZeFbzDNy7MFafvOzOTTFuN7bUrJoyIKqbnps3A1EIkF3Job0CAwEAAaBGMEQGCSqGSIb3DQEJDjE3MDUwMwYDVR0RBCwwKoITY2VydGJvdC0xLmJhci5sb2NhbIITY2VydGJvdC0yLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEANMlw7E1eLSaQwKMqvHKUcmaDDRNSa3VG6ekSCyVObVsVBg86hmkYhVmMtrfStCvM6O/wC1eAZez25TSeOcXd3Sxy3ScJLsgo9/wxNMnSngHFQcIU96EBdJeNYsMRkz0b0r2qA3FJKm5tpSCj5qZH+Cgm0y4Qwr0oD1LjMwHG0breqdizNxZGqGaCbNEWsHom6N2WU69xB5EQw+TSD9xoyL+WdvjJM6CM6EFlCFcwGu6iC6ypDJ4c17Ku+Zh7czs1XZEnW+sdgflvDxaPYp9092A947mSDXLGKJQZOk5viRd7h24MbgjPrzg3DLhvNZd+Of8caOQliZ+b1MU7IdVclw==\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 14:58:52\",\n        \"certificate.issue_uts\": 1596121132,\n        \"certificate.expire_uts\": 1627657132,\n        \"order.id\": 7,\n        \"order.name\": \"bZU4xme1P11D\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 14:58:49\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"certbot-2.bar.local\\\"}]\",\n        \"account.name\": \"brykxxUN24fZ\",\n        \"account.contact\": \"[\\\"mailto:certbot@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:58:26\",\n        \"account.jwk\": \"{\\\"e\\\": \\\"AQAB\\\", \\\"kty\\\": \\\"RSA\\\", \\\"n\\\": \\\"tdHHdbiipJL6KtTOY5cGAy15tHSit3lU-TrkgyrFwnl6tEghS_Seeu5D7aM95--_asRaQCRhukLLVzwlp4NZpx_NI-PUqcPGIOk4yQAuQuxKKcvDMBCo9tYXunVKox6iYnIPVAFFN_5YcQyImP3UTx0Gkv_qfaLCjaFlW33aGjp38EFuLExtXQ9ZseNHOzrOBlz331W6vbYsBVmyMqYMX_vAIxWMpkQSFdlX4n10BzOqoiNqNPHtoZw0chd_gBO8S-yRr8wR3KgFU6baog3Mm-er-I9vzxI7jCow7l_pQIU-p0X2dBaTnkpFJDlsFbSZnSObWe8kre1b7V1pukdpfw\\\"}\",\n        \"account.alg\": \"RS256\",\n        \"certificate.issue_date\": \"2020-07-30 14:58:52\",\n        \"certificate.expire_date\": \"2021-07-30 14:58:52\",\n        \"certificate.serial\": 3267690953728815010\n    },\n    {\n        \"certificate.id\": 5,\n        \"certificate.name\": \"Qnq7VE1wudyJ\",\n        \"certificate.cert_raw\": \"MIIEkjCCAnqgAwIBAgIIEmTp7OqCGmowDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNTAyMTNaFw0yMTA3MzAxNTAyMTNaMB4xHDAaBgNVBAMME2NlcnQuc3Rpci5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL7fFJ/qLoQQyiYTJhxCJRSFgdPPCr0mgBbQB6MUEFLu5MXjvosDM7cCX4YxTRZEVvevZcW/6jQsac0gb2cNWvJ9qtbuxMa8pjUw5kOy17ETzS1DTJf8EiP+xGUJzRPYFZRp0cBiJ58gDWyx6r1cC/7bDx96vaNF8QS8pEwG3Wl3e9eWgdB+49vNDLHuSlGo6qYwK5dkLTt5s8c4UvyefnXdFowZjiPYTUZZqwPqViIgkqtVbYpMNCVpgn5EIcstCvsQ3yRv+cjjbGPCAwqH2hrepl875QqqHdsCXm0fK1nPTCBxyMXR5cE7Ox+0B5PWac04jJDTa+BjslgkYHpeIHAgMBAAGjgccwgcQwCwYDVR0PBAQDAgXgMB4GA1UdEQQXMBWCE2NlcnQuc3Rpci5iYXIubG9jYWwwGAYIKwYBBQUHARoEDDAKoAgWBjEyMzQ1NjAdBgNVHQ4EFgQU4purbPFsQT7or7UxGmvCpoPe0gYwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQCy3VVi162R86K9Zs1qNPeHxyBmkyQINHcbxzKqzRT56FZD6n/Y/1yf3Ba/PQNUul/8KJVvOWJcdI1BoXxa+M0A7+0iddlvZndwq4kW664O+n4pdeuc5c3N2sMysrD4ojZ9DqEANuAqR8/yktdkIP3lArUX8rYjWbvDpqFjaPY1JDKsOcexRvmNpxFU4HoQFPgRHb4OWUtHa0DBbW0IM9EiPOK5HXYniPdHFapIno9r6hUwGkKbeKE2hpwKMjZuvv+2h8qSq+qtxQUs9PDe5vmSXBcqaKYZtVTz5p/Yb8pAUUfh3V5XAojHd348+UFZySPJgiBN5VJYGBRCBHAHIhUgv4sE0NjvCGE2X2MWf0mBJJ9+qL3rH4J2gCuW2nvZv+p/Vmv5PgEQCVXjj/bwJX7EzISThJHZ44NA0ny1c0ujCCzK7E0Qsko+8ySDiq+lONRKsq7wy9vW0h2lP5N9y7fMQf2S7t/+BWE6Hmt9NiGZnPRQJwh///yj92uc6A/n0sl1i5pMlLouauGmmmZC+z0lbFSR6HVu+yNSTBd1i3Xlz0cihXIqnkzcbFZbaYaUzSavyUxaw6l8RxLr1A4rOjdiNkswcY8CyGpTLox+WrviNwpSu0xl5dIRLQX9AmI6sEm24dw+MN8J0F95xQZD2XRMWi4l+GjbBVcG6YEKIpbYGQ==\",\n        \"certificate.csr\": \"MIICuzCCAaMCAQAwHjEcMBoGA1UEAwwTY2VydC5zdGlyLmJhci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMvt8Un+ouhBDKJhMmHEIlFIWB088KvSaAFtAHoxQQUu7kxeO+iwMztwJfhjFNFkRW969lxb/qNCxpzSBvZw1a8n2q1u7ExrymNTDmQ7LXsRPNLUNMl/wSI/7EZQnNE9gVlGnRwGInnyANbLHqvVwL/tsPH3q9o0XxBLykTAbdaXd715aB0H7j280Mse5KUajqpjArl2QtO3mzxzhS/J5+dd0WjBmOI9hNRlmrA+pWIiCSq1Vtikw0JWmCfkQhyy0K+xDfJG/5yONsY8IDCofaGt6mXzvlCqod2wJebR8rWc9MIHHIxdHlwTs7H7QHk9ZpzTiMkNNr4GOyWCRgel4gcCAwEAAaBYMFYGCSqGSIb3DQEJDjFJMEcwCwYDVR0PBAQDAgXgMB4GA1UdEQQXMBWCE2NlcnQuc3Rpci5iYXIubG9jYWwwGAYIKwYBBQUHARoEDDAKoAgWBjEyMzQ1NjANBgkqhkiG9w0BAQsFAAOCAQEAkUE5X1/Vx2QXlyTQ/QLyKxw18082uXV1L8i9Zr1GgtzOY2Qs4oFXSICuGQUaVVv7NihtQ4I/6b7TEgDXTUUffhlrQZ/lcTMIWrDP7+aL2nLU/Xhn2wC+wCK9eWpbZwAtBbK6khT8t95KO4K7Mfh/RKzDsXsFmx0q02iJ0KYt52tNsb9n6yAlZnCcJzAcD66nSMpi4R2/8pmMzw8toNrot5dENumMrH36RvtXArmhKQpYbtHN5iXWd59W9EA2LG7AUZHtS5Z6oklckzPBUKaetNuoeC4u3pTk3o6bbD6qoQagTHBBc4VJkc/SzgAoeumCs0PIbUNgVqtq42gtqHTbhQ==\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 15:02:13\",\n        \"certificate.issue_uts\": 1596121333,\n        \"certificate.expire_uts\": 1627657333,\n        \"order.id\": 8,\n        \"order.name\": \"BbYOopY9ged0\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 15:02:09\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"TNAuthList\\\", \\\"value\\\": \\\"MAqgCBYGMTIzNDU2\\\"}]\",\n        \"account.name\": \"QMSCUs0MH1j1\",\n        \"account.contact\": \"[\\\"mailto:grindsa3@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 14:52:20\",\n        \"account.jwk\": \"{\\\"crv\\\": \\\"P-256\\\", \\\"kty\\\": \\\"EC\\\", \\\"x\\\": \\\"-rKMCLQ5QsdEPxLUj_PCNmd1h2OE8ijI8w9KYkqBmuk\\\", \\\"y\\\": \\\"GWoJaVTgZBoE95pf0nvhcQcYy1VFadARtJxGMBJ-z9g\\\"}\",\n        \"account.alg\": \"ES256\",\n        \"certificate.issue_date\": \"2020-07-30 15:02:13\",\n        \"certificate.expire_date\": \"2021-07-30 15:02:13\",\n        \"certificate.serial\": 1325441394109717098\n    },\n    {\n        \"certificate.id\": 6,\n        \"certificate.name\": \"KQ10vV5vN0ja\",\n        \"certificate.cert_raw\": \"MIIDyTCCAbGgAwIBAgIIB6pJfF/ooycwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MzAxNTAzNTdaFw0yMTA3MzAxNTAzNTdaMBsxGTAXBgNVBAMTEGxlZ28tMS5iYXIubG9jYWwwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAS/l9y8W6W6zIAKrRYY1UOxB+xW/JpHcqLAk2B4PjDaZNT66IvD9U417Mh0RMdGpUbivp/QQv6fKeVOoh/GqagQt1dAIaSBl1jlT9eTeVHp9XmN14Fn5JcWPwJsYBHCSN2jga8wgawwLQYDVR0RBCYwJIIQbGVnby0xLmJhci5sb2NhbIIQbGVnby0yLmJhci5sb2NhbDAdBgNVHQ4EFgQUHFAp2Lkf29VqzuRqOOUX2LwALdcwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBn6/KOHlorWP9AClchORnqINknQ4+sVAP7sub6JoKv8aTJSGApIr+vd2dY8Yey/nfVWpVr5q2uRRU1gahFN5/Fjbk92Ll/lNIdHZGyX18ZF7fQSYzwdKqUSqOtLgKYqqWsCrWHFWNKmhC6GEhj6QXAOhDzwXnbxrPvIhgfC3ZFXm5LOLJexdFzC8qqUl2OCSKOFPywvCzJBbha3E3N0Snpxf1vWNdCNSuf3AlQzFCm0GMkBfpLbEQE2ELr3bhBrzVVtmAF/28Km6HvN2TUyF3VlLWHspaXsTuHrbFfY+rFtBIMTha1vnUMGuZio9acj5HOY2AqxdvC1xQ7inrHz6PMyHX7xtW1PoiI6qFU5GyluC7bS+MNhphJ8nv3sa4eQIubLUgmVFCLHf3qIpgQJjkxXQZOb5nnq0J4wISWwrjqYyZbM9f1/tTJ3YbeD4odjajW+j8Vf9aE0vlxHYLjBmpS185hew46M2foExaDhHI3TDfox9hE5Mln4BiWPGSm23I6TXJPv4LmBFHI3bimgt4zEdNcCEPkOjqvQM/hw9Fdva2ZACpEsOykE1NWSNywSriHT2Kzv9xeYs/uWzzu73xKXw3VpbrtO6GW9u+fca8+O8ioIZCZiHQlKvrPNWPll+yeaMiCG4vcdOy2RwqDywu1C9qW0BjTTZQh9u+quUiGWg==\",\n        \"certificate.csr\": \"MIIBVDCB2gIBADAbMRkwFwYDVQQDExBsZWdvLTEuYmFyLmxvY2FsMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEv5fcvFulusyACq0WGNVDsQfsVvyaR3KiwJNgeD4w2mTU+uiLw/VONezIdETHRqVG4r6f0EL+nynlTqIfxqmoELdXQCGkgZdY5U/Xk3lR6fV5jdeBZ+SXFj8CbGARwkjdoEAwPgYJKoZIhvcNAQkOMTEwLzAtBgNVHREEJjAkghBsZWdvLTEuYmFyLmxvY2FsghBsZWdvLTIuYmFyLmxvY2FsMAoGCCqGSM49BAMDA2kAMGYCMQD1Ge3x08RJgw5lgvQrQy2tfxb1TU2qUNfCUJuWI+TVFRYf7HrvvEwVYHlzyq/thoUCMQDwtnBIO6RTwS0SfYI9pZbEAgtlSCjdTZC3ZAXyzleJRaytx6JoZCrG8PKIjxyRDGU=\",\n        \"certificate.poll_identifier\": null,\n        \"certificate.created_at\": \"2020-07-30 15:03:57\",\n        \"certificate.issue_uts\": 1596121437,\n        \"certificate.expire_uts\": 1627657437,\n        \"order.id\": 9,\n        \"order.name\": \"AscxIwNhKwpm\",\n        \"order.status.name\": 5,\n        \"order.notbefore\": \"\",\n        \"order.notafter\": \"\",\n        \"order.expires\": \"2020-07-31 15:03:56\",\n        \"order.identifiers\": \"[{\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-1.bar.local\\\"}, {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"lego-2.bar.local\\\"}]\",\n        \"account.name\": \"Z9cKiTD0n3No\",\n        \"account.contact\": \"[\\\"mailto:lego@bar.local\\\"]\",\n        \"account.created_at\": \"2020-07-30 15:03:56\",\n        \"account.jwk\": \"{\\\"kty\\\": \\\"EC\\\", \\\"crv\\\": \\\"P-384\\\", \\\"x\\\": \\\"yUeSgdOA9cxu_OJVP_SNfk8qwe3YpWNjwJbhXkFsR-e2jakwfsut9mciFJJeJnZs\\\", \\\"y\\\": \\\"ca3rIm2ke2HyeADGd_wB_iNRgGcbx4sPRYFm9JeMVH-bDtM5m2uVhGiwk6H77xMy\\\"}\",\n        \"account.alg\": \"ES384\",\n        \"certificate.issue_date\": \"2020-07-30 15:03:57\",\n        \"certificate.expire_date\": \"2021-07-30 15:03:57\",\n        \"certificate.serial\": 552334702840161063\n    }\n]\n"
  },
  {
    "path": "examples/soap/mock_signer.py",
    "content": "#!/usr/bin/python3\n\"\"\"signing script to create a signed pkcs7 structure out of a pkcs7 csr\"\"\"\n# -*- coding: utf-8 -*-\n# pylint: disable=C0413, E0401, E0611, W0212\nfrom __future__ import print_function\nimport sys\nimport os\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom examples.ca_handler.pkcs7_soap_ca_handler import (\n    binary_read,\n    binary_write,\n    CAhandler,\n)  # nopep8\n\n\nif __name__ == \"__main__\":\n\n    DEBUG = True\n\n    LOGGER = logger_setup(DEBUG)\n\n    # check amount of command line arguments\n    if len(sys.argv) != 5:\n        LOGGER.error(\"Not enough command line arguments\")\n        sys.exit(1)\n\n    IN_FILE = sys.argv[1]\n    OUT_FILE = sys.argv[2]\n    SIGNER_ALIAS = sys.argv[3]\n    CONFIG_VARIANT = sys.argv[4]\n\n    if IN_FILE and OUT_FILE and SIGNER_ALIAS and CONFIG_VARIANT:\n\n        # load CSR\n        csr_der = binary_read(LOGGER, IN_FILE)\n\n        # SIGNER_ALIAS contains the signing cert\n        with open(SIGNER_ALIAS, \"rb\") as open_file:\n            signing_cert = x509.load_pem_x509_certificate(\n                open_file.read(), default_backend()\n            )\n\n        # CONFIG_VARIANT contains the signing cert\n        with open(CONFIG_VARIANT, \"rb\") as open_file:\n            signing_key = serialization.load_pem_private_key(\n                open_file.read(), password=b\"Test1234\", backend=default_backend()\n            )\n\n            ca_handler = CAhandler(DEBUG, LOGGER)\n\n        # decode signing cert\n        decoded_cert = ca_handler._cert_decode(signing_cert)\n\n        # create pkcs7 bundle and dump it to file\n        (_error, pkcs7_bundle) = ca_handler._pkcs7_create(\n            decoded_cert, csr_der, signing_key\n        )\n        binary_write(LOGGER, OUT_FILE, pkcs7_bundle)\n"
  },
  {
    "path": "examples/soap/mock_soap_srv.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"soap-server mock providing endpoint for soap ca handler\"\"\"\n# pylint: disable=c0413, e0401\nimport os\nimport sys\nimport argparse\nimport tempfile\nimport json\nimport subprocess\nfrom typing import List, Dict\nfrom http.client import responses\nfrom wsgiref.simple_server import WSGIServer, WSGIRequestHandler\nimport xmltodict\n\nsys.path.insert(0, \".\")\nsys.path.insert(0, \"..\")\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nfrom acme_srv.helper import (\n    b64_encode,\n    b64_decode,\n    b64_url_encode,\n    logger_setup,\n    convert_string_to_byte,\n    convert_byte_to_string,\n    load_config,\n)  # nopep8\n\n# pylint: disable=e0611\nfrom examples.ca_handler.xca_ca_handler import CAhandler  # nopep8\n\n\ndef arg_parse():\n    \"\"\"simple argparser\"\"\"\n    parser = argparse.ArgumentParser(description=\"soap server\")\n    parser.add_argument(\n        \"-d\", \"--debug\", help=\"debug mode\", action=\"store_true\", default=False\n    )\n    parser.add_argument(\n        \"-c\", \"--configfile\", help=\"config file\", default=\"soap_srv.cfg\"\n    )\n    parser.add_argument(\n        \"-e\",\n        \"--error\",\n        help=\"send soap error message\",\n        action=\"store_true\",\n        default=False,\n    )\n    parser.add_argument(\"-s\", \"--httpstatuscode\", help=\"http status code\", default=200)\n    args = parser.parse_args()\n\n    debug = args.debug\n    configfile = args.configfile\n    error = args.error\n    hsc = args.httpstatuscode\n\n    return debug, configfile, hsc, error\n\n\ndef _csr_get(\n    logger,\n    soap_dic: Dict[str, str],\n    soapenvelope: str,\n    soapbody: str,\n    aurrequestcertificate: str,\n) -> str:\n    \"\"\"get CSR from dictionary\"\"\"\n    logger.debug(\"_csr_extract()\")\n    aurrequest = \"aur:request\"\n    csr = None\n    if aurrequest in soap_dic[soapenvelope][soapbody][aurrequestcertificate]:\n        if (\n            \"aur:CertificateRequestRaw\"\n            in soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest]\n        ):\n            csr = soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest][\n                \"aur:CertificateRequestRaw\"\n            ]\n        if (\n            \"aur:ProfileName\"\n            in soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest]\n        ):\n            logger.info(\n                \"got request profilename: %s\",\n                soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest][\n                    \"aur:ProfileName\"\n                ],\n            )\n        if (\n            \"aur:Email\"\n            in soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest]\n        ):\n            logger.info(\n                \"got request email: %s\",\n                soap_dic[soapenvelope][soapbody][aurrequestcertificate][aurrequest][\n                    \"aur:Email\"\n                ],\n            )\n\n    return csr\n\n\ndef _csr_lookup(logger, soap_dic: Dict[str, str]) -> str:\n    \"\"\"get csr from soap request\"\"\"\n    logger.debug(\"_csr_lookup()\")\n    csr = None\n\n    soapenvelope = \"soapenv:Envelope\"\n    soapbody = \"soapenv:Body\"\n    aurrequestcertificate = \"aur:RequestCertificate\"\n\n    if soapenvelope in soap_dic and soapbody in soap_dic[soapenvelope]:\n        if aurrequestcertificate in soap_dic[soapenvelope][soapbody]:\n            csr = _csr_get(\n                logger, soap_dic, soapenvelope, soapbody, aurrequestcertificate\n            )\n\n    return csr\n\n\ndef _opensslcmd_pem2pkcs7_convert(\n    logger, tmp_dir: str, filename_list: List[str]\n) -> List[str]:\n    \"\"\"build openssl command\"\"\"\n    logger.debug(\"_opensslcmd_pem2pkcs7_convert()\")\n    cmd_list = [\n        \"openssl\",\n        \"crl2pkcs7\",\n        \"-nocrl\",\n        \"-outform\",\n        \"DER\",\n        \"-out\",\n        f\"{tmp_dir}/cert.p7b\",\n    ]\n\n    for filename in filename_list:\n        cmd_list.append(\"-certfile\")\n        cmd_list.append(filename)\n\n    return cmd_list\n\n\ndef _opensslcmd_csr_extract(logger, pkcs7_file: str, csr_file: str) -> List[str]:\n    \"\"\"build openssl command\"\"\"\n    logger.debug(\"_opensslcmd_csr_extract()\")\n    cmd_list = [\n        \"openssl\",\n        \"cms\",\n        \"-in\",\n        pkcs7_file,\n        \"-verify\",\n        \"-inform\",\n        \"DER\",\n        \"-noverify\",\n        \"-outform\",\n        \"PEM\",\n        \"-out\",\n        csr_file,\n    ]\n\n    return cmd_list\n\n\ndef _file_load_binary(logger, filename: str) -> List[str]:\n    \"\"\"load file at once\"\"\"\n    logger.debug(\"file_open(%s)\", filename)\n    with open(filename, \"rb\") as _file:\n        lines = _file.read()\n    return lines\n\n\ndef _file_load(logger, filename: str) -> List[str]:\n    \"\"\"load file at once\"\"\"\n    logger.debug(\"file_open(%s)\", filename)\n    with open(filename, \"r\", encoding=\"utf8\") as _file:\n        lines = _file.read()\n    return lines\n\n\ndef _file_dump_binary(logger, filename: str, data_: str):\n    \"\"\"dump content in binary format to file\"\"\"\n    logger.debug(\"file_dump(%s)\", filename)\n    with open(filename, \"wb\") as file_:\n        file_.write(data_)  # lgtm [py/clear-text-storage-sensitive-data]\n\n\ndef _file_dump(logger, filename: str, data_: str):\n    \"\"\"dump content to  file\"\"\"\n    logger.debug(\"file_dump(%s)\", filename)\n    with open(filename, \"w\", encoding=\"utf8\") as file_:\n        file_.write(data_)  # lgtm [py/clear-text-storage-sensitive-data]\n\n\ndef _pem2pkcs7_convert(logger, tmp_dir: str, pem: str) -> str:\n    \"\"\"convert pem bunlde to pkcs#7 by using openssl\"\"\"\n    certificate_list = pem.split(\"-----END CERTIFICATE-----\\n\")\n\n    filename_list = []\n    for cnt, certificate in enumerate(certificate_list):\n        if certificate:\n            certificate = f\"{certificate}-----END CERTIFICATE-----\\n\"\n            _file_dump(logger, f\"{tmp_dir}/{cnt}.pem\", certificate)\n            filename_list.append(f\"{tmp_dir}/{cnt}.pem\")\n\n    openssl_cmd = _opensslcmd_pem2pkcs7_convert(logger, tmp_dir, filename_list)\n\n    rcode = subprocess.call(openssl_cmd)\n\n    if not rcode:\n        content = b64_encode(logger, _file_load_binary(logger, f\"{tmp_dir}/cert.p7b\"))\n    else:\n        content = None\n\n    return content\n\n\ndef _get_request_body(environ: Dict[str, str]) -> str:\n    \"\"\"get body from request data\"\"\"\n    try:\n        request_body_size = int(environ.get(\"CONTENT_LENGTH\", 0))\n    except ValueError:\n        request_body_size = 0\n    if \"wsgi.input\" in environ:\n        request_body = environ[\"wsgi.input\"].read(request_body_size)\n    else:\n        request_body = None\n    return request_body\n\n\ndef _config_load(logger, config_file: str) -> Dict[str, str]:\n    \"\"\"load config file\"\"\"\n    config_dic = load_config(logger, cfg_file=config_file)\n\n    cfg_dic = {}\n    if \"CAhandler\" in config_dic:\n        if \"xdb_file\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"xdb_file\"] = config_dic[\"CAhandler\"][\"xdb_file\"]\n        if \"issuing_ca_name\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"issuing_ca_name\"] = config_dic[\"CAhandler\"][\"issuing_ca_name\"]\n        if \"issuing_ca_key\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"issuing_ca_key\"] = config_dic[\"CAhandler\"][\"issuing_ca_key\"]\n        if \"template_name\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"template_name\"] = config_dic[\"CAhandler\"][\"template_name\"]\n        if \"passphrase\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"passphrase\"] = config_dic[\"CAhandler\"][\"passphrase\"]\n        if \"ca_cert_chain_list\" in config_dic[\"CAhandler\"]:\n            cfg_dic[\"ca_cert_chain_list\"] = json.loads(\n                config_dic[\"CAhandler\"][\"ca_cert_chain_list\"]\n            )\n\n    return cfg_dic\n\n\ndef _csr_extract(logger, tmp_dir: str, csr: str) -> str:\n    \"\"\"extract csr from pkcs7 file\"\"\"\n\n    if csr:\n        # dump csr into a file\n        _file_dump_binary(logger, f\"{tmp_dir}/file.p7b\", b64_decode(logger, csr))\n        openssl_cmd = _opensslcmd_csr_extract(\n            logger, f\"{tmp_dir}/file.p7b\", f\"{tmp_dir}/csr.der\"\n        )\n        rcode = subprocess.call(openssl_cmd)\n\n        if not rcode:\n            content = convert_byte_to_string(\n                b64_url_encode(logger, _file_load_binary(logger, f\"{tmp_dir}/csr.der\"))\n            )\n        else:\n            content = None\n    else:\n        content = None\n\n    return content\n\n\ndef request_process(logger, csr: str) -> bytes:\n    \"\"\"construct soap response\"\"\"\n\n    tmp_dir = tempfile.mkdtemp()\n    config_dic = _config_load(logger, CONFIG_FILE)\n\n    ca_handler = CAhandler(True, logger)\n    ca_handler.xdb_file = config_dic[\"xdb_file\"]\n    ca_handler.issuing_ca_name = config_dic[\"issuing_ca_name\"]\n    ca_handler.issuing_ca_key = config_dic[\"issuing_ca_key\"]\n    ca_handler.template_name = config_dic[\"template_name\"]\n    ca_handler.passphrase = config_dic[\"passphrase\"]\n    ca_handler.ca_cert_chain_list = config_dic[\"ca_cert_chain_list\"]\n\n    # extract csr from pkcs7 construct\n    csr = _csr_extract(logger, tmp_dir, csr)\n    logger.debug(\"csr: %s\", csr)\n\n    # enroll certificate\n    (error, cert_bundle, _cert_raw, _unused) = ca_handler.enroll(csr)\n\n    if not error:\n        pkcs7 = _pem2pkcs7_convert(logger, tmp_dir, cert_bundle)\n    else:\n        pkcs7 = None\n\n    if not ERROR and pkcs7:\n        soap_response = f\"\"\"\n    <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n    <s:Body>\n        <RequestCertificateResponse xmlns=\"http://monetplus.cz/services/kb/aurora\">\n        <RequestCertificateResult xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">\n            <IssuedCertificate>{pkcs7}</IssuedCertificate>\n        </RequestCertificateResult>\n        </RequestCertificateResponse>\n    </s:Body>\n    </s:Envelope>\n    \"\"\"\n    else:\n        soap_response = \"\"\"\n    <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n    <s:Body>\n    <s:Fault>\n      <faultcode>s:Client</faultcode>\n      <faultstring>Processing RequestCertificate - Error! by request={ProfileName=fooba,CertificateRequestRaw.Length=2486,Email=foo@test.cz,ReturnCertificateCaChain=True}, profile=SSL 2Y AUTO, pkcs7initials=, ErrorMessage=Cannot parse PKCS7 message!</faultstring>\n    </s:Fault>\n    </s:Body>\n    </s:Envelope>\"\"\"\n\n    return convert_string_to_byte(soap_response)\n\n\ndef soap_srv(environ, start_response) -> List[str]:\n    \"\"\"echo application\"\"\"\n    request_body = _get_request_body(environ)\n    stack_d = xmltodict.parse(request_body)\n\n    csr = _csr_lookup(LOGGER, stack_d)\n\n    if csr:\n        # try:\n        status = f\"{HTTP_STATUS_CODE} {responses[int(HTTP_STATUS_CODE)]}\"\n        # Except Exception as error:\n        # status = \"500 Internal Server Error\"\n        headers = [(\"Content-type\", \"text/xml\")]\n        start_response(status, headers)\n        response = request_process(LOGGER, csr)\n    else:\n        status = \"400 OK\"\n        headers = [(\"Content-type\", \"text/html\")]\n        start_response(status, headers)\n        response = b\"Request malformed\"\n\n    return [response]\n\n\nif __name__ == \"__main__\":\n\n    (DEBUG, CONFIG_FILE, HTTP_STATUS_CODE, ERROR) = arg_parse()\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    httpd = WSGIServer((\"0.0.0.0\", 8888), WSGIRequestHandler)\n    httpd.set_app(soap_srv)\n    httpd.serve_forever()\n"
  },
  {
    "path": "examples/soap/soap_srv.cfg",
    "content": "[CAhandler]\nxdb_file: <path to xca.xdb>\nissuing_ca_name: <Name>\nissuing_ca_key: <Name>\ntemplate_name: <Name>\npassphrase: <phrase>\nca_cert_chain_list: [\"<Name>\"]\n"
  },
  {
    "path": "examples/trigger/certifier_trigger.sh",
    "content": "#!/bin/bash\n# trigger script for Insta Certifier / NCM\n# we expect the path to certificate submitted as $1\n# commandline for publishing method \"/usr/local/certifier/bin/trigger.sh %cert\"\n# import certificate format must be changed to PEM to interwork with certifier_ca_handler.py\n\n# URL to acme2certifier\nACME2CERTIFIER_URL='http://192.168.14.1/trigger'\n\n# thats the relative path to cert\nCERT_FILE=\"${1}\"\n\n# certifier base directory\nCERTIFIFER_BASE='/usr/local/certifier'\n# absolute path to cert\nCERT_PATH=\"${CERTIFIFER_BASE}/${CERT_FILE}\"\n\n# certificate object in base64\nSTR_BASE64=$(cat \"${CERT_PATH}\" | base64 -w 0)\nPAYLOAD='{\"payload\": \"'\"${STR_BASE64}\"'\", \"signature\": \"foo\"}'\n\n# post command\ncurl -X POST -H \"Content-Type: application/json\" -d \"${PAYLOAD}\" \"${ACME2CERTIFIER_URL}\"\n\nexit 0\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"acme2certifier\"\nversion = \"0.41.3\"\ndescription = \"ACMEv2 server\"\nreadme = \"README.md\"\nauthors = [\n    { name = \"grindsa\", email = \"grindelsack@gmail.com\" }\n]\nlicense = { file = \"LICENSE\" }\nrequires-python = \">=3.7\"\ndependencies = [\n    \"setuptools\",\n    \"jwcrypto\",\n    \"cryptography\",\n    \"pyOpenssl\",\n    \"dnspython\",\n    \"pytz\",\n    \"configparser\",\n    \"python-dateutil\",\n    \"requests\",\n    \"pysocks\",\n    \"josepy\",\n    \"acme\",\n    \"xmltodict\",\n    \"pyasn1\",\n    \"pyasn1_modules\",\n    \"requests_pkcs12\",\n    \"requests_gssapi\",\n    \"gssapi\",\n    \"pyyaml\",\n    \"idna\",\n    \"werkzeug>=3.0.6\",\n]\nclassifiers = [\n    \"Programming Language :: Python\",\n    \"Development Status :: 4 - Beta\",\n    \"Natural Language :: German\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)\",\n    \"Operating System :: OS Independent\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/grindsa/acme2certifier\"\n\n[tool.setuptools]\ninclude-package-data = true\n"
  },
  {
    "path": "requirements.txt",
    "content": "setuptools\njwcrypto\ncryptography\npyOpenssl\ndnspython\ncertsrv[ntlm]\npytz\nconfigparser\npython-dateutil\nrequests\npysocks\njosepy\nacme\nxmltodict\npyasn1\npyasn1_modules\nrequests_pkcs12\nrequests_gssapi\ngssapi\npyyaml\nidna\nwerkzeug>=3.0.6 # not directly required, pinned by Snyk to avoid a vulnerability\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"build script for acme2certifier\"\"\"\n\nimport pathlib\nimport typing as t\nfrom setuptools import setup\nimport shutil\nfrom glob import glob\nfrom acme_srv.version import __version__\n\n\ndef glob_files(pattern: str) -> t.List[str]:\n    return [\n        str(file_) for file_ in pathlib.Path(\".\").glob(pattern) if not file_.is_dir()\n    ]\n\n\n# Update nginx config files and copy them to /var/lib/acme2certifier/examples/nginx\ndef update_and_copy_nginx_configs():\n    src_dir = pathlib.Path(\"examples/nginx\")\n    dst_dir = pathlib.Path(\"/var/lib/acme2certifier/examples/nginx\")\n    dst_dir.mkdir(parents=True, exist_ok=True)\n    configs = [\n        \"nginx_acme_srv.conf\",\n        \"nginx_acme_srv_ssl.conf\",\n        \"supervisord.conf\",\n        \"uwsgi.service\",\n        \"acme2certifier.ini\",\n    ]\n    # Define allowed configurations to prevent path injection\n    allowed_configs = {\n        \"nginx_acme_srv.conf\",\n        \"nginx_acme_srv_ssl.conf\",\n        \"supervisord.conf\",\n        \"uwsgi.service\",\n        \"acme2certifier.ini\",\n    }\n\n    for conf in configs:\n        # Ensure only filename is used, preventing path traversal attacks\n        safe_filename = pathlib.Path(conf).name\n        src_file = src_dir / safe_filename\n        dst_file = dst_dir / safe_filename\n\n        # Validate filename is in allowed list and contains no path separators\n        if conf not in allowed_configs or \"/\" in conf or \"\\\\\" in conf or \"..\" in conf:\n            print(f\"Warning: Skipping invalid config file: {conf}\")\n            continue\n\n        if src_file.exists():\n            content = src_file.read_text()\n            content = content.replace(\n                \"/var/www/acme2certifier/volume/acme2certifier_cert.pem\",\n                \"/etc/ssl/certs/acme2certifier_cert.pem\",\n            )\n            content = content.replace(\n                \"/var/www/acme2certifier/volume/acme2certifier_key.pem\",\n                \"/etc/ssl/private/acme2certifier_key.pem\",\n            )\n            content = content.replace(\n                \"/var/www/acme2certifier\", \"/var/lib/acme2certifier\"\n            )\n            content = content.replace(\"/opt/acme2certifier\", \"/var/lib/acme2certifier\")\n            content = content.replace(\n                \"/run/uwsgi/acme.sock\", \"/var/lib/acme2certifier/acme.sock\"\n            )\n            content = content.replace(\n                \"uid = nginx\", \"uid = www-data\\nplugins = python3\"\n            )\n            content = content.replace(\"chown-socket = nginx\", \"chown-socket = www-data\")\n            dst_file.write_text(content)\n        else:\n            print(f\"Warning: {src_file} not found.\")\n\n\nupdate_and_copy_nginx_configs()\nsetup(\n    name=\"acme2certifier\",\n    version=__version__,\n    description=\"ACMEv2 server\",\n    url=\"https://github.com/grindsa/acme2certifier\",\n    author=\"grindsa\",\n    author_email=\"grindelsack@gmail.com\",\n    license=\"GPL\",\n    include_package_data=True,\n    data_files=[\n        (\"/usr/share/doc/acme2certifier/\", glob_files(\"docs/*\")),\n        (\n            \"/usr/share/doc/acme2certifier/architecture\",\n            glob_files(\"docs/architecture/*\"),\n        ),\n        (\"/var/lib/acme2certifier/acme_srv/\", glob_files(\"acme_srv/*.py\")),\n        (\n            \"/var/lib/acme2certifier/acme_srv/helpers\",\n            glob_files(\"acme_srv/helpers/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/acme_srv/challenge_validators\",\n            glob_files(\"acme_srv/challenge_validators/*.py\"),\n        ),\n        (\"/var/lib/acme2certifier/examples\", glob_files(\"examples/*.*\")),\n        (\n            \"/var/lib/acme2certifier/examples/ca_handler\",\n            glob_files(\"examples/ca_handler/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/db_handler\",\n            glob_files(\"examples/db_handler/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/eab_handler\",\n            glob_files(\"examples/eab_handler/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/hooks\",\n            glob_files(\"examples/hooks/*.py\"),\n        ),\n        (\"/var/lib/acme2certifier/examples/django\", glob_files(\"examples/django/*.py\")),\n        (\n            \"/var/lib/acme2certifier/examples/django/acme2certifier\",\n            glob_files(\"examples/django/acme2certifier/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/django/acme_srv\",\n            glob_files(\"examples/django/acme_srv/*.py\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/django/acme_srv/fixture\",\n            glob_files(\"examples/django/acme_srv/fixture/*\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/django/acme_srv/migrations\",\n            glob_files(\"examples/django/acme_srv/migrations/*.py\"),\n        ),\n        (\"/var/lib/acme2certifier/examples/nginx\", glob_files(\"examples/nginx/*\")),\n        (\"/var/lib/acme2certifier/examples/trigger\", glob_files(\"examples/trigger/*\")),\n        (\"/var/lib/acme2certifier/tools\", glob_files(\"tools/*.py\")),\n        (\"/var/lib/acme2certifier/examples/Docker\", glob_files(\"examples/Docker/*.*\")),\n        (\n            \"/var/lib/acme2certifier/examples/Docker/wsgi\",\n            glob_files(\"examples/Docker/wsgi/*\"),\n        ),\n        (\n            \"/var/lib/acme2certifier/examples/Docker/django\",\n            glob_files(\"examples/Docker/django/*\"),\n        ),\n    ],\n    platforms=\"any\",\n    classifiers=[\n        \"Programming Language :: Python\",\n        \"Development Status :: 4 - Beta\",\n        \"Natural Language :: German\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)\",\n        \"Operating System :: OS Independent\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n    ],\n    install_requires=[\n        \"setuptools\",\n        \"jwcrypto\",\n        \"cryptography\",\n        \"pyOpenssl\",\n        \"dnspython\",\n        \"pytz\",\n        \"configparser\",\n        \"python-dateutil\",\n        \"requests\",\n        \"pysocks\",\n        \"josepy\",\n        \"acme\",\n        \"xmltodict\",\n        \"pyasn1\",\n        \"pyasn1_modules\",\n        \"requests_pkcs12\",\n        \"pyyaml\",\n        \"idna\",\n        \"werkzeug>=3.0.6\",  # not directly required, pinned by Snyk to avoid a vulnerability\n    ],\n    zip_safe=False,\n    test_suite=\"test\",\n)\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectKey=grindsa_acme2certifier\nsonar.organization=grindsa\nsonar.python.coverage.reportPaths=coverage.xml\nsonar.issue.ignore.multicriteria=e1,e2,e3,e4,e5,e6,e7\nsonar.issue.ignore.multicriteria.e1.ruleKey=githubactions:S7637\nsonar.issue.ignore.multicriteria.e1.resourceKey=**/*.yml\n# curl http\nsonar.issue.ignore.multicriteria.e2.ruleKey=githubactions:S6573\nsonar.issue.ignore.multicriteria.e2.resourceKey=**/*.yml\n# glob fales positive\nsonar.issue.ignore.multicriteria.e3.ruleKey=githubactions:S4830\nsonar.issue.ignore.multicriteria.e3.resourceKey=**/*.yml\n# python Calls should not be made to non-callable values\nsonar.issue.ignore.multicriteria.e4.ruleKey=python:S5756\nsonar.issue.ignore.multicriteria.e4.resourceKey=**/*.py\n# long line in docker file\nsonar.issue.ignore.multicriteria.e5.ruleKey=docker:S7020\nsonar.issue.ignore.multicriteria.e5.resourceKey=**/Dockerfile\n# usage of http\nsonar.issue.ignore.multicriteria.e6.ruleKey=githubactions:S5332\nsonar.issue.ignore.multicriteria.e6.resourceKey=**/*.yml\n# non enforcement of tls\nsonar.issue.ignore.multicriteria.e7.ruleKey=githubactions:S6506\nsonar.issue.ignore.multicriteria.e7.resourceKey=**/*.yml\n# This is the name and version displayed in the SonarCloud UI.\n#sonar.projectName=acme2certifier\n#sonar.projectVersion=1.0\n\n# Path is relative to the sonar-project.properties file. Replace \"\\\" by \"/\" on Windows.\n#sonar.sources=.\n\n# Encoding of the source code. Default is default system encoding\n#sonar.sourceEncoding=UTF-8\n"
  },
  {
    "path": "test/__init__.py",
    "content": ""
  },
  {
    "path": "test/ca/certs.p7b",
    "content": "-----BEGIN PKCS7-----\nMIIK9AYJKoZIhvcNAQcCoIIK5TCCCuECAQExADALBgkqhkiG9w0BBwGgggrHMIIF\nTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UECxMO\nYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1NDAw\nWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEPMA0G\nA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxXHa\nGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8jqqx\nkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/qkzm\noO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT//Wrs\nChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCVXcB9\nftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9hcym\nqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLBZQjb\nCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB15Y5f\n0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilMGueQ\nihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8hH2C\nKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dmKxzt\nWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0TAQH/\nBAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYDVR0P\nAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNh\nIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc31UV\nyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77WvDhy\n1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP96YrR\ncHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqHJh4s\nKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa7gdQ\nJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwCzM4p\ntXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ32tUi\nl9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/M7sI\nBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5Z3XW\nWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsFzfv6\nERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4tjX1v\nlY35Ofonc4+6dRVamBiF9DCCBXAwggNYoAMCAQICCHry008TjKGYMA0GCSqGSIb3\nDQEBCwUAMCsxFzAVBgNVBAsTDmFjbWUyY2VydGlmaWVyMRAwDgYDVQQDEwdyb290\nLWNhMB4XDTIwMDUyNzAwMDAwMFoXDTMwMDUyNjIzNTk1OVowKzEXMBUGA1UECxMO\nYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCcuFGR3WYGLeuJP6xWpqAuu+rWL7Wm3roqlcNXOqFW\nSPPe3BSxugWMMq9hGo+7Ra6kyQ3jDeL2UrnS7Jiw6upvCsF/64j81EyJXIzOWiDA\nDWa/ayxLNzVrXIr6JQeWkNbJYXVYrVDy7sbBZ2HkE8sRcj+5Z4PTP2eNNyixvKYX\nZiozNLyZGo+Drijl391LFqlkGMkZf5rNO8VY9NrqtPC5KHjvo7UIrL8lV1EXWgnH\nmbciv2QUOzRQrGytddnFUdXtmiaJezSQAOlpuogcwHZAANpd5IeNEi6BG2omlTIs\nSzdr4pSGTjgKA11+Pk+oq/ipw1UidsruXPziTMLl8B64ey4INb7BUeUzXoZJ4Y1L\nljlDvtE5Cj4NgOyk4O9jmdpjnC2SG8c69T+UUb3Zi0Cz60xdhCb6UDzZm16jd2VV\nhL3x045JExWP3bDk7xU4Eq4tec2CnIfL6LXFO8/gUIYJjLcDtiYTzJmegAXfbJCO\n4o1qcDpbQIcbXaATuk+ggQqxNsl3Olfz8sgCnBYJTZiIIbeaF7JxPrm/3bcfH/SH\nmv8TT3aOWhsvH3WoraJQytepHdym+zhOBzByMDscRdQRAKnq8cYWyzEQa/IUUmSO\nXLy81i76QEOc7oYw+ld2/QWBXeLowLt85d5m83W2IxaBjl+mgWWhg8ZXODtlux8Z\n7QIDAQABo4GXMIGUMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAxlb7kxWGbR\nAJM92n/cESgw7wLMMB8GA1UdIwQYMBaAFAxlb7kxWGbRAJM92n/cESgw7wLMMA4G\nA1UdDwEB/wQEAwIBhjARBglghkgBhvhCAQEEBAMCAAcwHgYJYIZIAYb4QgENBBEW\nD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAgEAI5KO3V/ogoE/ptyM\ntVYOo+zEXrzwM6tZah0UTTXbdnLed9KSLrexb+VdsWJN+ZGvovxvl8jr012vbwFC\nogbYkZ1D7F7uFHEuwnKwlxMx8eHjrR56ecA4TtBcefzlGU2j/i+z3dRg/4/ed4m8\neWzGvzPUY/kuPOp7Lee7bg0ZhAGrxQ4jHei94x+GEnJI5iB9rkngyGkWvZOmoNO+\n15lob1WEPbbke0Rm0rrldxXOqanBW21qaC4TUXBXPoW1CjrCpdnD/kBCj/x9rN1j\nJZaoACimXjvtSjrBqNQg7OO+WyF8ggaRWx9hzj2rZrt8mUUPX4/bWvRFuOp99wxU\nTS4pxVmuFibOMi1T9Y7oHtkwjsgNbetYsvvkUV0ht3uzRWKxGbgobdRHQwHcyFGP\nPZvTQj8KA2EuAmoYJ35JDu2EHun+sqiuorMn/GRXKdbhefHhEQ4hfxQ6kvJy5gnV\nEbiOegw6Dbw6YoOSf/UTVhvLxL7dqe0K43mdxAGrceSbmLcvzSkx2cCVpOdQoMxI\nMw8MxNiSIqnpXbS+XdEpenlbr9BtOARMrl8RFqYFVcwUuKhBSAUp0yc4LtT15iHd\n8i74Nja/DSr8MmZjecShadAPQVqNec1tT1w2g/pd/aE2A+2oI15M0wI2CHHzSewE\nyGBQ9MQLzQDn+9LO2jbqRX75BtmhADEA\n-----END PKCS7-----\n"
  },
  {
    "path": "test/ca/certs.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\njX1vlY35Ofonc4+6dRVamBiF9A==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\nfvkG2Q==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/certsrv_ca_certs.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFizCCA3OgAwIBAgIQTMegIi+5oJVMhnXtdJZylzANBgkqhkiG9w0BAQsFADBY\nMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFDASBgoJkiaJk/IsZAEZFgRuY2xtMRgw\nFgYKCZImiZPyLGQBGRYIbm90YWRlbW8xDzANBgNVBAMTBlJvb3RDQTAeFw0yMDA5\nMDEwNjQ1MDJaFw00MDA5MDEwNjU1MDFaMFgxFTATBgoJkiaJk/IsZAEZFgVsb2Nh\nbDEUMBIGCgmSJomT8ixkARkWBG5jbG0xGDAWBgoJkiaJk/IsZAEZFghub3RhZGVt\nbzEPMA0GA1UEAxMGUm9vdENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC\nAgEAxMIVkgSvPB6ggSpVrcmk/9XNnAUdSU8Eleqg7MCBWqReLHl4NL7CbdwFJr49\nYHmhuoanuhnOFNVDMzJa8qry8Nb52Istnw6bbkTqNkMOoU2ocDZBb8n4cpOoYhaB\n52u8cmIUR1vM3OBnELS+/FKbU6c/EN/T77WKdcDYs0VJSS0moGZcMFGuB0EJDiM/\nuxjhE8I7+sdRK174tW6Lm72MAMwfFQbOqa1ufHAFjzPB/V966Kdzx8EFW2O8RXbM\noL2uPaz/4323iEYNc93XY38gg+NykvicvAj9+xdE5hO3Kue7AAAf782yghqWpW1+\n6LJtv7iwFZYOFPn6VWynU4RDBEF7Jz7lIV+uyexs1KTDACgNFUVCEzvQtl2ubw9t\nP9WJrFwb8IZxx40ObrP6OzuY0T6T8Bc4Yq5pF23446ftQutz0ekBpHKAtWOmgpeR\nw6C2svb7531/IADqjYx9JlvGhzEI7ytIel9HQajYR6/HeP4mE6qgeU0OAKn4pDhy\nNj/Q2SRCFnsXnn8d6rz1cwnyxXoZeziUL8u81xogiAUxx8KuJwMdtDlkRL7fAe8x\nsYEeVi18FOjQuMLy8hE9XEGybvRZyCwkHtKdReMM1LMwwUSkiOwOZuFNe3o/9BAf\nYa65OQCiyPmTpRrg4PBS/rWjrFJpM4nn/nhWSwy7QUgC84ECAwEAAaNRME8wCwYD\nVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK2mJQL1KxBrlbWh\nvwF8Qk18z7VnMBAGCSsGAQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBCwUAA4ICAQCz\nM9zPg69pXE0TZRL8rMcct77cnQUkBR8TLM3S8p5CshYEmthgS1REbeFzTbO+QHcm\ngicRk29QkOG+Huu9LaHtAzgikpGiPe8sRjlFvXliefBjFtbeUxUdPm7WwyNC4KYP\nr9eg8svEKEiPtegwonyw5r/+CvExpdQVnxP9U9I7+TKASgS+6mSsCoTzwLCnIrNp\nZBThb8pIXmJNx8KWNzRE08SJY91VonqAVRGMqDta9u8FdA1rv+m7yuG2FitCMMRn\nSYsVoqFyKsUW+WEiSOG78MbIIFtzpZ0Te1SVbvc+Th1PdXSXCHr+/exW2BUFBjgz\nl7Jesy9iBYWg8PUucpPZHJG2qhB6B4KHn2b4PikGWHR7h60BR9M+zLalzGbHPWc0\np5FnWcRE9/FCVbuIfNoocaA4t8MvYcYyXOuqR0NeFHbai4WTbKZzwyfGX34oA36U\nHd8FPH7Rom8vdK06pJvjyEriKBaE9Yzh17j5A+q0KOteDk0TOHpZjsqnpf8yo9f6\nX/I0KQ03VKj0W2GkC5Kft4nZYIcoHfYJAMRLOkzcid1ko6Kn1kfZ4fPaNiL9PsGN\nN9kNxQHy8R9wEvB0Kl393mymH+A/d+QuU8CrzG8hxc0mPwiPnjesGsv/jQ6KheIM\nKBJgf/D2RRMDq1/EeIEBDS1b1beTqeDcirbjAbqkMw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/fr1.txt",
    "content": "_fakeroot-cert-1\n"
  },
  {
    "path": "test/ca/fr2.txt",
    "content": "_fakeroot-cert-2\n"
  },
  {
    "path": "test/ca/root-ca-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFHzCCAwegAwIBAgIISRJolCjaE+8wDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nAxMHcm9vdC1jYTAeFw0yMDA2MDkxNzE3MDBaFw0zMDA2MDkxNzE3MDBaMBIxEDAO\nBgNVBAMTB3Jvb3QtY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk\nUX6DpcKks9qIbIstBjYvDAcU0hvypQwILvDpCFSy9uZ8SO9PRSfK0ltxljmu3ESS\n62OuGO8s9+HJm7rxkXVX6YttnYU60KrlmiRS9Js0BRj2jfL4E4ydNc6cIE4perh9\n3ydQJmaS16KiNmEC+XE/5mBpI4qQrRm2obYOloyxShFc3CzsJe55iyhtclZf1L/6\nJNgc3t6Q4RGiGnvXv2nJOI0BnnC9QY0DtZITuXK/43RES08MEizgpsTJz0QQ2ugJ\nFJa5SzbPC8f1hzTYctB7LLPc6nFpECESZ4zplAu/5ruD/7jtkwNBWeGo+kPCkyHx\nAL/XQH42VpM8iQbN61CWboV4ibYhP9/ko1oyM5UV5+UPqYEsao4MSJ3oblQEFHta\nSrOsy7Z6ZXW0eYK8Z/iYjXZ2wSskJis3K7sh/9oPrm1jlAXwpEGsJlE6xobrnWHz\n5c3/PxNvKj3U5jdiJhVcJxvMFpHL5fLqLQU4aXwsZIvPOyM7wZyLrmWbkzvmcg2t\n4TWwJSlVf2QGIZ4C8HsXnZJoM+GhVMGJpNJAJ7XzmI6ExmCwEcBbfkPQXHuB9hGb\nfRQRRR+/eXXjU62VyxpbwWNygxHsigV3bc1GGwK613Y9t5l1I4DMTB150dliNT7f\n+nWzsA3GdBTj/frBOclVjXRLqt5sE/SvMkHwYd7+7QIDAQABo3kwdzASBgNVHRMB\nAf8ECDAGAQH/AgECMB0GA1UdDgQWBBS/zoiPYe7Wln8qrB80MMIqtzQbzjAfBgNV\nHSMEGDAWgBS/zoiPYe7Wln8qrB80MMIqtzQbzjAOBgNVHQ8BAf8EBAMCAQYwEQYJ\nYIZIAYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBCwUAA4ICAQCkJd69cYr4CKechyDM\nTGivDBmqmEt3WDo5jNSbckJ+4gSUD+TcSfyUqXbKGAM5d9unZrnDLfh256IsYyyz\noAkzDi5LhS2umKK6cc5XyZFZ6c6JwYfwjw3+7GMSKrzLPwCqQYcJjjs/W9OppKkB\nmWwsDA6H5124sQLeHFwFYmjm2LFlNZ+tsYsAPJVmxFnodLjOPEbbFbrDHq5fgbd+\nVKfC9UCXd7OuP6J2MQ6D8SEaynWKp/PsXHk4xAzMrRdg3UjvHMLEcuQXzogzy84V\n1gYTMrrLDPg/wXCzUM1lZbHhFriQAWXGCDu3tpr1yS08K7YayHo32QbSMzfXnWCp\nLhAV2AI5DA9lBJ8O6ImyNyIm0xiWbiofBmEUw0MaKgTRWWMnIFvLgZH70PJOZw9o\ndIHPnXy0aLYm/6jePIg/MYmGW4+3LJMG9AmA3QgzFEvJUIT4NFxh0eNOCEh9cmoz\noISMy0toJRm7YbY2AvtuKoEASWkAyHgPaF7NDvHCqySXTsxzE5O+hiFp3X1byiRm\ncsixfvC0BVYX4CpxLWUX8Et3mrv42Pd/ZtxtCpwv0G5w+X4OAlOg1DBX5gsf1i+3\nYIIt3y0Bsw1UsRSRgUvaMZCWvKOtCfBYc5HjLS74RijIm9OaiDc2LDQOUloiT3xd\npLo0ga8P7AMdeSpyJF1qdsTqxA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/root-ca-client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDOjCCAiKgAwIBAgIIZfAJqucZROAwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UE\nAxMOcm9vdC1jYS1jbGllbnQwHhcNMjAwNjA5MTc0MjAwWhcNMjEwNjA5MTc0MjAw\nWjAZMRcwFQYDVQQDEw5yb290LWNhLWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAJv2TZ9tYz/1ZQWRs53wOWSdU6m3sFznnmuDkAM4T5MnT8fY\nErT4ua2PrF4LdFvV1OlAF2eilWzO60YwYW+asmvSm+qS9W40+AmzQnnjxQiKeBMy\nk99+p+IXPyRSdQb21kZysjq6dq5emGQlLf5HfUgiixzSSIOUwPxfgHh2+YFbosYu\nZG5hasaKoMyU+vV5cimYGeeuXU+zHnoakglL4X2NLeKFTFTWedjscKkBvT4mB0HS\n0SkP6mSByGGav8nyW/lS7wyr+DcKAp3AGYUtbRTJZr1awNFCEnFrUsbHGA1dUILm\nllIUgie5puktK5e/IRyYz5dDam+GXUQ60JsDiNMCAwEAAaOBhTCBgjAMBgNVHRMB\nAf8EAjAAMB0GA1UdDgQWBBQz7Ypv/M4QV9oyomlJSzZLf/7TmzALBgNVHQ8EBAMC\nA7gwEwYDVR0lBAwwCgYIKwYBBQUHAwIwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCG\nSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAED/\n3jth/abuEdwoqRsWm4gZdOvP5xTpCdL5bOTxQU4vJmNNpEku/vUnuSunx8mbdvNL\ndTi18Uduz68lQehcPwuxwa/kyKNNEAKjOJDUisgcNCKRkTKNr+vwxkFufAlDW1/2\n1kEjMIEebepeQO4JA03MQbdtODnDaP4gVv4LjtsIpqh8ikEB+ZlSntkkHXa5T50d\nJqmVL3yIfhr8GgEaD042bei9XVRDul4Cb9GMrtqt1zhXxFULTC/kVfabAufBEZef\nFrRDVJ9/+zNIkUnlaAYkRWHUU3q6H2gm+NTZvN7msHemwEBKIeVdoC8bmZASPt+I\nEiL82fepbqCmxcGwEu0=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/root-ca-client.txt",
    "content": "MIIFHjCCAwagAwIBAgIIcFUYZLICUHowDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nAxMHcm9vdC1jYTAeFw0yMDA2MDkxNzE4MDBaFw0zMDA2MDkxNzE3MDBaMBExDzAN\nBgNVBAMTBnN1Yi1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO2x\nCXITgVcFJuqh5ibdBUwaNJgI3lBmG2aLjvIaBXbH3ocH6WsXO1d5i37PepArmTpG\nfd+Ew3FvUmyiPzFO3WJSwgk096yoLVQHwAcxyuy1dBXemGFBm3v5ofTK+MyINxJP\nPkRWbJLhSETofzCo/vbFkBqIkFoarh8uEwfxzhpDq3cWjROaJhHhT+6GsbDMGouL\nza9J1HVs4Fn5fkmFxB9g7st5Z19TVIKCisPP4VQNmbhaWuHJMkaXDkaVAUcw6QQy\nquwfdl78jndh14D8tFuoLZcRxT/cfQPmqhcjjkkTelMk/5OfYlk+1wjoAEToXDzG\nHTkXS/zkGMdZeZFstXAFhy0THlSMO5RTuKwSXxIObvKKM2WdNVpnYOD5XaZnvFJx\n5fQu13HiNrprQKQAssihKqz9iCeJ0xuwuIydkrmjXSU0KtyzSZXBgaaMSeRYx+dG\nhVfv1f6U+MQjBq+QEbkuafcFs3TnuRcbbWA4H+T4cJK3krg9m9s1P2tTAFYuNDAR\nDf6IgPPyeSglcMpKPqg7uZWR5e/0wQj29XLUgrkFoIPsLEML6aKTMOdiCUcdAdne\nmkwcB7JIth+HAeSjNpW1lRb14QNxbf52qxN0X8HsDNYmg++rxxOOwEMzYuo7Ch1C\nlzI0RGaRHHbkWymj+doyXvVwnV4ZZwRXXpeJmhmTAgMBAAGjeTB3MBIGA1UdEwEB\n/wQIMAYBAf8CAQEwHQYDVR0OBBYEFIMnznmJq0PWTssJTOYznKF7gjN4MB8GA1Ud\nIwQYMBaAFL/OiI9h7taWfyqsHzQwwiq3NBvOMA4GA1UdDwEB/wQEAwIBBjARBglg\nhkgBhvhCAQEEBAMCAAcwDQYJKoZIhvcNAQELBQADggIBAJMwQ33+tL2yO+tePbDZ\nb5ZiASeAI6FmHWLtTpx3YeDBXR1NFtMGR1jYiWO5Mc6BAmLC2KPevXt+VM7RWeQU\nPlMKfcbxX5XdrhMWyC0HSvGQtg0/Ftc6do8Mj3wOIrmlskJPDX9s4VT0ersabRdZ\n2Djkcyub5PipMcJcPR7IpnfLJLcK1/QxrAHZtdseUuOyvXelAoPWTPLk4uMxEzGk\nlTDhqjCzVc3+sRJOwV9QQopOX3tB5vijAzC96jrRj90UNDZBt9IJu0sBIYro/a9O\nAqxFbQXcsr6rUZgvktWgghqJOai/iidTCA0TM4SqJUuaok4guJmMTGc8mJZ+EfmV\n8JPJ/8OaP2KU2VcMDzqN4/KeZt7+tfG7GB3h6l3D7M/q8qkSYYo2nzQ405EB/KVY\nohhw85RcJw3zqnziFuTmTTSqu4zOLlGFwSri7VgH3JfmgGWmjQ6B8wP3V0jKydFb\nJaIM4PLPRZeCHfexJ3Iu2a174saShxBwBsfCVah+9j6PJ8Pvx8LzPW+WeyW56NpF\nFamwsJJKqafvBMuZPjCR+burDPJh9Y7IrPZNDsgmBcUzB9BCxB1Qlj4fdkieuHJM\n5m6jaOeUlnECprkyutyh5lEY0BVw5RO+v9SqtI/6cbFrclMcb1SpF5W0slEX3xTG\n02BvBlCOKrZlqnp/jSdmAZ9s\n"
  },
  {
    "path": "test/ca/sub-ca-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFHjCCAwagAwIBAgIIcFUYZLICUHowDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nAxMHcm9vdC1jYTAeFw0yMDA2MDkxNzE4MDBaFw0zMDA2MDkxNzE3MDBaMBExDzAN\nBgNVBAMTBnN1Yi1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO2x\nCXITgVcFJuqh5ibdBUwaNJgI3lBmG2aLjvIaBXbH3ocH6WsXO1d5i37PepArmTpG\nfd+Ew3FvUmyiPzFO3WJSwgk096yoLVQHwAcxyuy1dBXemGFBm3v5ofTK+MyINxJP\nPkRWbJLhSETofzCo/vbFkBqIkFoarh8uEwfxzhpDq3cWjROaJhHhT+6GsbDMGouL\nza9J1HVs4Fn5fkmFxB9g7st5Z19TVIKCisPP4VQNmbhaWuHJMkaXDkaVAUcw6QQy\nquwfdl78jndh14D8tFuoLZcRxT/cfQPmqhcjjkkTelMk/5OfYlk+1wjoAEToXDzG\nHTkXS/zkGMdZeZFstXAFhy0THlSMO5RTuKwSXxIObvKKM2WdNVpnYOD5XaZnvFJx\n5fQu13HiNrprQKQAssihKqz9iCeJ0xuwuIydkrmjXSU0KtyzSZXBgaaMSeRYx+dG\nhVfv1f6U+MQjBq+QEbkuafcFs3TnuRcbbWA4H+T4cJK3krg9m9s1P2tTAFYuNDAR\nDf6IgPPyeSglcMpKPqg7uZWR5e/0wQj29XLUgrkFoIPsLEML6aKTMOdiCUcdAdne\nmkwcB7JIth+HAeSjNpW1lRb14QNxbf52qxN0X8HsDNYmg++rxxOOwEMzYuo7Ch1C\nlzI0RGaRHHbkWymj+doyXvVwnV4ZZwRXXpeJmhmTAgMBAAGjeTB3MBIGA1UdEwEB\n/wQIMAYBAf8CAQEwHQYDVR0OBBYEFIMnznmJq0PWTssJTOYznKF7gjN4MB8GA1Ud\nIwQYMBaAFL/OiI9h7taWfyqsHzQwwiq3NBvOMA4GA1UdDwEB/wQEAwIBBjARBglg\nhkgBhvhCAQEEBAMCAAcwDQYJKoZIhvcNAQELBQADggIBAJMwQ33+tL2yO+tePbDZ\nb5ZiASeAI6FmHWLtTpx3YeDBXR1NFtMGR1jYiWO5Mc6BAmLC2KPevXt+VM7RWeQU\nPlMKfcbxX5XdrhMWyC0HSvGQtg0/Ftc6do8Mj3wOIrmlskJPDX9s4VT0ersabRdZ\n2Djkcyub5PipMcJcPR7IpnfLJLcK1/QxrAHZtdseUuOyvXelAoPWTPLk4uMxEzGk\nlTDhqjCzVc3+sRJOwV9QQopOX3tB5vijAzC96jrRj90UNDZBt9IJu0sBIYro/a9O\nAqxFbQXcsr6rUZgvktWgghqJOai/iidTCA0TM4SqJUuaok4guJmMTGc8mJZ+EfmV\n8JPJ/8OaP2KU2VcMDzqN4/KeZt7+tfG7GB3h6l3D7M/q8qkSYYo2nzQ405EB/KVY\nohhw85RcJw3zqnziFuTmTTSqu4zOLlGFwSri7VgH3JfmgGWmjQ6B8wP3V0jKydFb\nJaIM4PLPRZeCHfexJ3Iu2a174saShxBwBsfCVah+9j6PJ8Pvx8LzPW+WeyW56NpF\nFamwsJJKqafvBMuZPjCR+burDPJh9Y7IrPZNDsgmBcUzB9BCxB1Qlj4fdkieuHJM\n5m6jaOeUlnECprkyutyh5lEY0BVw5RO+v9SqtI/6cbFrclMcb1SpF5W0slEX3xTG\n02BvBlCOKrZlqnp/jSdmAZ9s\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/sub-ca-client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEGDCCAgCgAwIBAgIJALL8aztMPfV2MA0GCSqGSIb3DQEBCwUAMEgxCzAJBgNV\nBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xFzAVBgNVBAoMDkFjbWUyQ2VydGlmaWVy\nMQ8wDQYDVQQDDAZzdWItY2EwHhcNMTkwNjI1MDEyNTAwWhcNMjAwNjI1MDEyNTAw\nWjBPMQswCQYDVQQGEwJERTEPMA0GA1UEBxMGQmVybGluMRcwFQYDVQQKEw5BY21l\nMkNlcnRpZmllcjEWMBQGA1UEAwwNY2xpZW50X3N1Yi1jYTCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBALvoKKg3ciBVWZtquiWyMogWU6ydEfmLbXktK6T+\nowxzxHVaoePVGH9DZvTZD2pHS8xJ6fpFr3pZYiuqiUHuxdMpj9gVxik5ivBrSJIk\nZXLxwvNJWpMa1o1Hxz1By3Hrlm3ebKIzfQPqRRcdjWtJgCFbcTpalwhE1RQFMp4I\ncb08aAE9uEaZQ4uZ8Ls30J6IHC4PG63lGI1tkAtLIoUWupRAmnWDx0ysXzXeN7m+\nLff9ols9MZNgzRMgY/zGUq0LzZfi+L+Iev3sztCdoIOBA/K63jv0hOPyYg331L05\nXIwbLeUoUG41J4pZzafx6MAFp4Zam1w+aafCzEw7ZPHQvn0CAwEAATANBgkqhkiG\n9w0BAQsFAAOCAgEABPgWo4KAXJNXNfEBbixDuCxtwO1JuphSOTcpIlEp+uNOSDzg\nNEbrhUXTNM8SPshzFjBpudc29okiyC62CfLD/X+EvIeKo/oa477kN6MuNfqLGZ42\na935ES3S00Wy8rbwyIoPCsKWT/6VsHRHUn8XhFNFUBKZ8FGxwXcAVpPanyikURqV\nH1MgAk62hJQdYjSxdga/GKS1dS39fyxQz7uBPt5WIQZPzL6dr2Yn/4lQUvTUVus2\ne1cTh3z02yB5EDlEAcMMvMNpfYvNdU5H6QEPwysbkW9E/Ep84aq21zwuPxICh0Kd\njHWKkHtCqDoEYIADDl1AD5UdJTMQ9LIzUjsBvtB5I6yT7jgsx/iqTDrkJVK/zRf4\nNeKRa3AW57jsPUIcUstUFnVJbg+MM4fYmapx8Hqm/Aq+II9ip80AM6hXvierTQn4\nMNQivL0ZJfj0Ro9KEIDAHN3IAfIlFovbkBPLMi9PtfyhuVmXpthE9OaDlgUguWb4\n5LAKwgfu1TFGPPpf5jTw2qVx0F+iCiUwK8ZgnakkXOKE5+KIb8ejL+3pPd5Wt+45\nw/7gEFOjT6XAzZGnUtcMH/lpxmgbl3/SKkyrW4h7PnF2FEEVC4XnZuQm+ZwD/PpX\nfmAA52ygKHBzUr9V33CkW0FhvjqkAUya5x9CqWlHoal0RVvFavnw+4ImqbE=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/ca/sub-ca-client.txt",
    "content": "MIIEQjCCAiqgAwIBAgIIH8Sx9i0Cyv0wDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGc3ViLWNhMB4XDTIzMDYwMzA0NTEwMFoXDTI3MDYwMzA0NTEwMFowEzERMA8GA1UEAwwIYWNtZV9zcnYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNKBsIwDu+XamKWBjQ69goq+p/m6AIRYu+cq0kSVAsHzz6pRR08bpRoW0iNEhXMpNMgu6LXyvOHNocqtZrV8JG7MYLzHJPKq9I6nZtox9hdTbUIOMIiwidqrHlW1Q1SZhFP36IMgRhC6lcT5SZWqJ+e+DKxQvp+kW+aGCGUfnAoT6CAG8r/KnqXYKESYP2NImW8Gve8LqqMn1CBAX/WwCW3YSg2ujjtyosFNP5WMDKeyykFx8Q0ztCUyCNx/JV7OXGdgTuEzwmwlZxbF7yJvYMyB8wlXer3XUsxcRb72HkWGCV9r7fSXubtd97wMT5iWemcFUV3aRDcx69muqrZePnAgMBAAGjgZswgZgwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUx4wG0mnZeNn5fDE2ma1wzwW18XgwCwYDVR0PBAQDAgPoMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQGA1UdEQQNMAuCCWFjbWVfc3J2LjARBglghkgBhvhCAQEEBAMCBkAwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAgEA40IQHxMGJKiIsgqESNCZGGHZjCGgRU6yrzQlPAxVhnZzuyRU1x4ITogJzio4DZUnDTJ00rloGJ6BqBtjoc64UaKBrM6rbypl4xknAvzmmgHX17A1i7/dkThVvg2twG9zDOCEQeUs61b5jnJ4D023tLFtyOItnuKRgtpKQwqKnGndyE1HqRPoD6aFNBcpt43BRRi8D2xUMGYbvzho7uJdvw4Wq5kiYRcyiX87RSv2yu/poYorv5otYlYgy5fqG44c5UY2QvcdXsDY97eGxrDOKRtTkTZIDkZDDR7m9Mayh9jPTtlT+6uUlDlnpGa1OjPfQ7BSmmp2FpJCttbSWOFh3KN633aveSKU+FUys1H6AGVVTMIu2f8NsZz7bS4gkY5Vwu2MX2Dgf5o640yeCHMCZGqx4R8Qa2AnjkQ+aU9z4BOLxGRJFC8zFthyjZu5N+AhZLAFXcWmC+dAU04+e4y0G4UgJGYJnFgKWtE8RR1/XPePE7/CMZpEEohRCnlFjREBZxLHMoMTLV+eOG+kdtOQe1Cs/ImErJ5cQZDwXxpfGwCjUoMqCv1wj69KGNfFTUoMpzAzpBFmkyLXHGqD0iP9ZmT5I2aRhydgjy3fTDgzgLX7ECetVXXHNkE+oVGLym/12VqfsG3PcutjR6gI4b/Oqq3YDI83zIvIw91wRaKgyuQ\n"
  },
  {
    "path": "test/ca/sub-ca-crl.pem",
    "content": "-----BEGIN X509 CRL-----\nMIICozCBjAIBATANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZzdWItY2EXDTIw\nMDYxMDEzMDkwMFoXDTIwMDcxMDEzMDkwMFowNzA1AggLzDDFRO8mpBcNMjAwNjEw\nMTMwODU1WjAaMBgGA1UdGAQRGA8yMDIwMDYxMDEzMDgwMFqgDjAMMAoGA1UdFAQD\nAgEBMA0GCSqGSIb3DQEBCwUAA4ICAQABHQgUzMBI1Zy7MhXnUOmOVm7gr80L8LJP\nsbouAXKP2tVyU2NHNGqErhbEk9NdNxSGnbnMKdqIwLTOqLGvLNqlIEOFpUdUieF7\nEtHx9qFBAar7oW0DH5DEpCVlMGjnskJ2OX2e6m2/wfGdkSNJ8TDq+xinS70MvBC/\n35pNPrw47LfzHIMGL/mbscVYCQBfjigLrJ1p+q76tt71ksLa3UKOjjabFryKLFwq\nx6A3J3WqVo0iVtxQ1eG/6dekDD2EjU5HvUY/OOsp+f+zyX3UP0kuL2trOC1fvyh5\nFx3xds1NRT/P7J6ceIBTcsolXRci6yXVzZEvPQoSqbTb8lih349MOw3Ybnr4QEKa\nofGmgB40t2iIe3xxbgfxEaosvlS092aKav2vXfbPGyOfEFQzU2ed8LYrnGR0Ispo\nCi8DPtDd/tw+1+JFjvQfcOZ1LY0rxw5Z9PWzZmJK/+bUhkmJfk66paJ9S6psP25d\npCJZJdSBToAVx9+i9EUdjOWJm71bet8WTEIz2/XFa133qqNaeiQMQsjRFk+tDsf/\nvlJK0CL1zq+0sGWK24r6tNBFZtz3cLwjCmMRQP7uRuUvkEZDN0kg2xaI0aAh6Qy/\nTZfkJ31EcAk8cmiovFAocxcKXkMAzCzhgEMJS6uH1fnZRkEp7FGSC5skE4oS8y5h\noFoxO8J0Mg==\n-----END X509 CRL-----\n"
  },
  {
    "path": "test/ca/sub-ca-key.pem",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIJrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIuFQimxPhneMCAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCndDcHL89th8/HZ/NSTEaaBIIJ\nUOl1kg/KJmnsAT/Ufm2NEaEQptAwq6SqP1BXPNQxadLifJG2J4C1jwRzGpefInpx\nN27mHKYrPk3+37a+oKLdNGiPe/ZMVsURLP0CSHbkmmlbWcwX/o+XmiErWxS0jilz\n+da0siCL8PKKLZGRxpeA4SWQj1/umPTzE1qyDSHevpr2H840JRGJSe8Z9tbn/smz\nSiI19gPBONpqxqWI3WPMd8Z2EUPp7k0aONMtiOBpJSalECk1UfE7u3Yqk/CCBiYK\n+B+oelkx9QtnFL1h/fKfv1QWapYrHQxSYRRGiWqQs+gSIGytI0V0rJIuODBPwtf0\nixwsgDROga0t/sYsL2qmlAgT5y2mkqGFAndrZYNCh0Qo0WMn1iXgg8srEAYeNG96\nH2CR0JN1bYHR8VRku/vdI9Za/qHeFWqBMkt7Re11Qf1KhnaFvxasBBVhf8bYXjEE\nAtKQtRYgTWTzQTheUyWGSbJiiwE0qlZwqeBAtQsTGyifpmFrw5e3OjKHI6P8hpyo\n8IHss543fzlK2+JoiYTLHdPjKcV9YUgz418YwE+3/0GeNxF4O16MHbcfw8QQ0LIl\n8FpWqgVEK1AAg305pMxawAoXAdi1ait6YY/kTwJr8N0nL21Foxjq0jO1TmPhRv3O\nAKyVhoCaWxRelIoVTv0iVCGbr8XuLqYZpCQvkpOpBihaMvPPsbWyLa1yxDX0WEE2\nprRlC/ZJK9nOgzaOKr/7a2oaFmyB7pWZqUbwZ+Kgux/5bdDMoy+ziODT7nrZScQE\nA6ZZ1yv5cQTyccQ4r9pEX0ouqXrTuW2XNs8IETJfUF0lxn9D+58lJTMGLd+T6UUM\nLP30CoL7D9PyBPeDnD2zD7WG8HcBHEqil/x/jE/mmxF9VMBInBxlKWAfCFloyFl3\nCY47/bLA7UAmz2C5m/cfAJY6CcZVITQdZPJShziVo+0lQMOsVRzywoIMMPB4ZIwh\nuV4k98BdK6g1n07CHnTIx5xubLM5GTPsjvu6y2/Wk94kEBn8a21Ev0jsRl/qJW4u\nbklvEe2IE7qXYLG4TZTyywY4DvH8y3Dst5/NEqXuNeG3q+/ifzxA/hJZXegkQX2P\nBxIgdcSEoh2Ha/1yyr43uWGf1p6nlP84At/aHxxpYMXhhDB735mr38e48Jq86TK7\noGDERnT8UgNljtru6Oq+IpdZg4UaR9/LYrC3W50l54BYotN1e+l9EjOY1uG2D6D6\nuTPWZdhJa0LhqmwFq3AL2fB+2gbdKu6V2P37MB6SiPvtLjqPV6e4lninmjT9HoID\n9IA5KGzyVOBZQPpqhvNYuq+YaaE3rx4/s3jGYhspWkoGiXWDDNoT0aMzEypb8Ztz\nV58QNuumZKunJ9RSb4NsXWreptMTE+ugQvLprMzIb7W1ekq4HmraVgV5vpWWvMYu\ns7qPq+w6I/DTjATDLVYtM+y/ATjbQrDnNCEpix9uYqJCIuxrYuRwM30HPnyRFDJa\nEfX4ZL83kUsFLWp5MBTb88MYXGnPEjS6M5EzqxUYtzI3+qk6P+57FGB5QOMfOMDy\n7Ujzw6hK1EFqANPm/yGZyxvDp9yKYoQnG6Ycb4MVe4BxQlhANTpGv+uNhMBeRbwu\nBrMZf9LUe0WpX7gnCGoCLQP3T4+No8FC4KhihiZsB6QEJlRj/8WvmMEufhgbtZN6\nhsivCiOF1lJszfz98v3uzWwdvrSwkyJkvwbPlV1DOx5VsuYvgAF70vH9ChneeI2T\n4fRrCiM2MUGM56MkBkEtAqSagF2B08v6Nx2O28zfllV2pfn/97rWwcKSGM7kez2T\nLZhIuY2scxkt5bcZSzs8pum2muCAs7HNNOAskQ5dIaaEupc8BP9EuoRe4jes7EvU\nBCW6bA+aMV+C06YUum99f5ywlU1pH5LOuImiM+AD1HtrWy/LJjlLj3zY8pcGGL01\nze3ILR+fZzTbRQjZeLx9imQJlIJrKsC0VCC6M5DtbPixcNenhFHn5TMIVYUB/ORW\n/uCU/xSR3MO3s71AJIt/TBCZDw6D82Pmf7nef5s8xZqo6G9IY2pDIbNxbY+hHIRW\nRIxpj7PvViETHPFV/6SMEdxWaSZVtcdLAl7o40YKApM1p5ZBBdWuwyBQGH/B72D8\nJCsPXJQaCk2F3qlEXTGEkEQJnLQ1Ec3rLXKu1V5C6qwKnaJGi68ccE/oNEhgzcs7\n/uD495/0LZhy8xTZpiiasekdGBmaTm522ii4RNJcGxznibpB+sYwBa8CqOdF4l49\n8D6x3o3726F+hMPz68XMhg2099RRkn646lscG7Mguu0Qr9iAYyJdZq6F8qX1SsVs\nP1ZB0PHuoFFJ/DCq4txaNN3sECT+0U9fqs7JgO8j39NNe7HlUL+UU8D/2M820T6D\nf7go5zVctr8jyI47yZ2KvTEBJGRrY6zSAqLehxh7xC1yBvuSpjDOaZD77Vq0Dnwp\nCvSEGuOuW9djFX3rFT+MgtpgSqn/HlONyRrPUb7tMYZSID0KqJZ2BPA8mGYz2F34\nUf1yyD24epn9AZDzjVdvEnlHCzzhPW89pNgCv3aMr9PpQPfmA7I6wX8+W0Rjbu8f\n5H6Vv893OkxOLpj8XTaFR/dHHKrvbnPwU3dUC93BdaKnMaa6zkm9hUNaTW3ggr2r\nJsu5l1lJtb7dune/LvupbjFqDDZ9mFSteE1c0SHH1uZ1scTs4nnTANfMVheVK94H\nt+/kWdSQ3xv6QeB1CWPLmi8zvDVrknC0RdmBfpZT7jcNJGg6q19Xy5J1XBTQZDHt\nXohxQvQddqlsgmoMN9PtbinkdQe1T4OtsNx23Gv2Gj4hTenGIVTPasRGF8Rd0n3B\noCj+zXzKTQmOBspv4SSXabL4pFY9hy5BHXFNxo6KbG/BfI7LKrMLv+Tr6EbfSlbH\nF2Ce4BDwTrP4OntSzKiU9Uvx/veO10hUUEsFmJY2wSQSorthJedhSzo6nE5AgXNH\n8SeBo6Ic5MpYfXaGj/9Ep86DCiNox1BmrPe1BeL3LRX7pTNwMmBsvDuXdClaZr3x\nx7BwCmRcrwrej9EYugf2sDxFWpXivwDemhiDaCrP+oLpmo0KBPwV2pUI7Tnm/qyO\n509l4/j8h9g1uA9FhRMiZ77NXTKOI+MtU41hjdufxRbiVvwrbB35tB/yntGkVrfI\n3st+irRHTZBqMJlAIoWsloLQkEI1kwAAntdFmQ9QVU8s\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "test/test_account.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport importlib\nimport configparser\nimport sys\nfrom unittest.mock import patch, MagicMock, Mock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestAccountRepository(unittest.TestCase):\n    \"\"\"test class for AccountRepository\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.dbstore = MagicMock()\n        from acme_srv.account import AccountRepository\n\n        self.account_repository = AccountRepository(self.dbstore, self.logger)\n\n    def test_001_lookup_account_success(self):\n        \"\"\"test enter\"\"\"\n        self.account_repository.dbstore.account_lookup.return_value = {\n            \"account\": \"account\"\n        }\n        self.assertEqual(\n            self.account_repository.lookup_account(\"field\", \"value\"),\n            {\"account\": \"account\"},\n        )\n\n    def test_002_lookup_account_exception(self):\n        \"\"\"test enter\"\"\"\n        self.account_repository.dbstore.account_lookup.side_effect = Exception(\n            \"DB error\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.account_repository.lookup_account(\"field\", \"value\")\n        self.assertIn(\n            \"Failed to look up account: DB error\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error during account lookup: DB error\",\n            log_cm.output,\n        )\n\n    def test_003_add_account_success(self):\n        \"\"\"test add_account success\"\"\"\n        self.account_repository.dbstore.account_add.return_value = (\n            \"test_account\",\n            True,\n        )\n        self.assertEqual(\n            self.account_repository.add_account({\"name\": \"test_account\"}),\n            (\"test_account\", True),\n        )\n\n    def test_004_add_account_exception(self):\n        \"\"\"test add_account exception\"\"\"\n        self.account_repository.dbstore.account_add.side_effect = Exception(\"DB error\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.account_repository.add_account({\"name\": \"test_account\"})\n        self.assertIn(\"Failed to add account: DB error\", str(context.exception))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error while adding account: DB error\",\n            log_cm.output,\n        )\n\n    def test_005_update_account_success(self):\n        \"\"\"test update_account success\"\"\"\n        self.account_repository.dbstore.account_update.return_value = True\n        self.assertTrue(\n            self.account_repository.update_account({\"name\": \"test_account\"})\n        )\n\n    def test_006_update_account_exception(self):\n        \"\"\"test update_account exception\"\"\"\n        self.account_repository.dbstore.account_update.side_effect = Exception(\n            \"DB error\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.account_repository.update_account({\"name\": \"test_account\"})\n        self.assertIn(\"Failed to update account: DB error\", str(context.exception))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error while updating account: DB error\",\n            log_cm.output,\n        )\n\n    def test_007_delete_account_success(self):\n        \"\"\"test delete_account success\"\"\"\n        self.account_repository.dbstore.account_delete.return_value = True\n        self.assertTrue(self.account_repository.delete_account(\"test_account\"))\n\n    def test_008_delete_account_exception(self):\n        \"\"\"test delete_account exception\"\"\"\n        self.account_repository.dbstore.account_delete.side_effect = Exception(\n            \"DB error\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.account_repository.delete_account(\"test_account\")\n        self.assertIn(\"Failed to delete account: DB error\", str(context.exception))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error while deleting account: DB error\",\n            log_cm.output,\n        )\n\n    def test_009_load_jwk_success(self):\n        \"\"\"test load_jwk success\"\"\"\n        self.account_repository.dbstore.jwk_load.return_value = {\"jwk\": \"value\"}\n        self.assertEqual(\n            self.account_repository.load_jwk(\"test_account\"), {\"jwk\": \"value\"}\n        )\n\n    def test_010_load_jwk_exception(self):\n        \"\"\"test load_jwk exception\"\"\"\n        self.account_repository.dbstore.jwk_load.side_effect = Exception(\"DB error\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.account_repository.load_jwk(\"test_account\")\n        self.assertIn(\"Failed to load JWK: DB error\", str(context.exception))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error while loading JWK: DB error\",\n            log_cm.output,\n        )\n\n\nclass TestExternalAccountBinding(unittest.TestCase):\n    \"\"\"test class for ExternalAccountBinding\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.eabhandler = MagicMock()\n        from acme_srv.account import ExternalAccountBinding\n\n        self.eab = ExternalAccountBinding(\n            self.logger, self.eabhandler, \"http://tester.local\"\n        )\n\n    def test_001_get_kid_success(self):\n        \"\"\"test get_kid success\"\"\"\n        # Simulate a valid protected header (base64 encoded JSON)\n        import base64\n\n        protected = base64.b64encode(b'{\"kid\": \"test_kid\"}').decode()\n        self.assertEqual(self.eab.get_kid(protected), \"test_kid\")\n\n    def test_002_get_kid_invalid(self):\n        \"\"\"test get_kid invalid input\"\"\"\n        # Simulate invalid base64 or JSON\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n            self.assertIsNone(self.eab.get_kid(\"invalid_base64\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to decode protected header:\", log_cm.output[0]\n        )\n\n    def test_003_compare_jwk_success(self):\n        \"\"\"test compare_jwk success\"\"\"\n        import base64\n\n        protected = {\"jwk\": {\"kty\": \"oct\", \"k\": \"abc\"}}\n        payload = base64.b64encode(b'{\"kty\": \"oct\", \"k\": \"abc\"}').decode()\n        self.assertTrue(self.eab.compare_jwk(protected, payload))\n\n    def test_004_compare_jwk_mismatch(self):\n        \"\"\"test compare_jwk mismatch\"\"\"\n        import base64\n\n        protected = {\"jwk\": {\"kty\": \"oct\", \"k\": \"abc\"}}\n        payload = base64.b64encode(b'{\"kty\": \"oct\", \"k\": \"xyz\"}').decode()\n        self.assertFalse(self.eab.compare_jwk(protected, payload))\n\n    def test_005_compare_jwk_no_jwk(self):\n        \"\"\"test compare_jwk no jwk in protected\"\"\"\n        self.assertFalse(self.eab.compare_jwk({}, \"payload\"))\n\n    def test_006_verify_signature_success(self):\n        \"\"\"test verify_signature success\"\"\"\n        content = {\"foo\": \"bar\"}\n        mac_key = \"key\"\n        # Patch Signature.eab_check to return (True, None)\n        with patch(\"acme_srv.signature.Signature.eab_check\", return_value=(True, None)):\n            result, error = self.eab.verify_signature(content, mac_key)\n            self.assertTrue(result)\n            self.assertIsNone(error)\n\n    def test_007_verify_signature_failure(self):\n        \"\"\"test verify_signature failure\"\"\"\n        content = {\"foo\": \"bar\"}\n        mac_key = \"key\"\n        with patch(\n            \"acme_srv.signature.Signature.eab_check\", return_value=(False, \"error\")\n        ):\n            result, error = self.eab.verify_signature(content, mac_key)\n            self.assertFalse(result)\n            self.assertEqual(error, \"error\")\n\n    def test_008_verify_signature_no_content(self):\n        \"\"\"test verify_signature with no content or mac_key\"\"\"\n        result, error = self.eab.verify_signature(None, None)\n        self.assertFalse(result)\n        self.assertIsNone(error)\n\n    def test_009_verify_success(self):\n        \"\"\"test verify success\"\"\"\n        payload = {\n            \"externalaccountbinding\": {\"protected\": \"eyJraWQiOiAidGVzdF9raWQifQ==\"}\n        }\n        self.eabhandler.return_value.__enter__.return_value.mac_key_get.return_value = (\n            \"key\"\n        )\n        with patch(\"acme_srv.signature.Signature.eab_check\", return_value=(True, None)):\n            code, message, detail = self.eab.verify(\n                payload, {\"unauthorized\": \"unauthorized\"}\n            )\n            self.assertEqual(code, 200)\n            self.assertIsNone(message)\n            self.assertIsNone(detail)\n\n    def test_010_verify_signature_error(self):\n        \"\"\"test verify signature error\"\"\"\n        payload = {\n            \"externalaccountbinding\": {\"protected\": \"eyJraWQiOiAidGVzdF9raWQifQ==\"}\n        }\n        self.eabhandler.return_value.__enter__.return_value.mac_key_get.return_value = (\n            \"key\"\n        )\n        with patch(\n            \"acme_srv.signature.Signature.eab_check\", return_value=(False, \"error\")\n        ):\n            code, message, detail = self.eab.verify(\n                payload, {\"unauthorized\": \"unauthorized\"}\n            )\n            self.assertEqual(code, 403)\n            self.assertEqual(message, \"unauthorized\")\n            self.assertEqual(detail, \"EAB signature verification failed\")\n\n    def test_011_verify_no_mac_key(self):\n        \"\"\"test verify no mac_key found\"\"\"\n        payload = {\n            \"externalaccountbinding\": {\"protected\": \"eyJraWQiOiAidGVzdF9raWQifQ==\"}\n        }\n        self.eabhandler.return_value.__enter__.return_value.mac_key_get.return_value = (\n            None\n        )\n        code, message, detail = self.eab.verify(\n            payload, {\"unauthorized\": \"unauthorized\"}\n        )\n        self.assertEqual(code, 403)\n        self.assertEqual(message, \"unauthorized\")\n        self.assertEqual(detail, \"EAB kid lookup failed\")\n\n    def test_012_check_success(self):\n        \"\"\"test check success\"\"\"\n        import base64\n\n        protected = {\"jwk\": {\"kty\": \"oct\", \"k\": \"abc\"}}\n        payload = {\n            \"externalaccountbinding\": {\n                \"payload\": base64.b64encode(b'{\"kty\": \"oct\", \"k\": \"abc\"}').decode(),\n                \"protected\": base64.b64encode(b'{\"kid\": \"test_kid\"}').decode(),\n            }\n        }\n        self.eabhandler.return_value.__enter__.return_value.mac_key_get.return_value = (\n            \"key\"\n        )\n        with patch(\"acme_srv.signature.Signature.eab_check\", return_value=(True, None)):\n            code, message, detail = self.eab.check(\n                protected,\n                payload,\n                {\n                    \"unauthorized\": \"unauthorized\",\n                    \"malformed\": \"malformed\",\n                    \"externalaccountrequired\": \"externalaccountrequired\",\n                },\n            )\n            self.assertEqual(code, 200)\n            self.assertIsNone(message)\n            self.assertIsNone(detail)\n\n    def test_013_check_jwk_mismatch(self):\n        \"\"\"test check jwk mismatch\"\"\"\n        import base64\n\n        protected = {\"jwk\": {\"kty\": \"oct\", \"k\": \"abc\"}}\n        payload = {\n            \"externalaccountbinding\": {\n                \"payload\": base64.b64encode(b'{\"kty\": \"oct\", \"k\": \"xyz\"}').decode(),\n                \"protected\": base64.b64encode(b'{\"kid\": \"test_kid\"}').decode(),\n            }\n        }\n        code, message, detail = self.eab.check(\n            protected,\n            payload,\n            {\n                \"unauthorized\": \"unauthorized\",\n                \"malformed\": \"malformed\",\n                \"externalaccountrequired\": \"externalaccountrequired\",\n            },\n        )\n        self.assertEqual(code, 403)\n        self.assertEqual(message, \"malformed\")\n        self.assertEqual(detail, \"Malformed request\")\n\n    def test_014_check_no_externalaccountbinding(self):\n        \"\"\"test check no externalaccountbinding\"\"\"\n        protected = {\"jwk\": {\"kty\": \"oct\", \"k\": \"abc\"}}\n        payload = {}\n        code, message, detail = self.eab.check(\n            protected,\n            payload,\n            {\n                \"unauthorized\": \"unauthorized\",\n                \"malformed\": \"malformed\",\n                \"externalaccountrequired\": \"externalaccountrequired\",\n            },\n        )\n        self.assertEqual(code, 403)\n        self.assertEqual(message, \"externalaccountrequired\")\n        self.assertEqual(detail, \"External account binding required\")\n\n    def test_016_verify_no_kid(self):\n        \"\"\"test verify branch where eab_kid is None (line 91)\"\"\"\n        payload = {\"externalaccountbinding\": {\"protected\": \"invalid_base64\"}}\n        code, message, detail = self.eab.verify(\n            payload, {\"unauthorized\": \"unauthorized\"}\n        )\n        self.assertEqual(code, 403)\n        self.assertEqual(message, \"unauthorized\")\n        self.assertEqual(detail, \"EAB kid lookup failed\")\n\n\nclass TestAccount(unittest.TestCase):\n    \"\"\"test class for Account\"\"\"\n\n    @patch.dict(\"os.environ\", {\"ACME_SRV_CONFIGFILE\": \"ACME_SRV_CONFIGFILE\"})\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.account import Account\n        from acme_srv.message import Message\n        from acme_srv.signature import Signature\n\n        self.account = Account(False, \"http://tester.local\", self.logger)\n        self.account.repository = MagicMock()\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.signature = Signature(False, \"http://tester.local\", self.logger)\n\n    def test_017__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.account.__enter__()\n\n    def test_018__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.account.__exit__()\n\n    def test_001_create_account_success(self):\n        \"\"\"test create_account success\"\"\"\n        content = {\"protected\": {}, \"payload\": {}}\n        with patch.object(self.account, \"message\") as mock_message:\n            mock_message.check.return_value = (200, None, None, {}, {}, None)\n            with patch.object(\n                self.account,\n                \"_create_account\",\n                return_value=(200, \"test_account\", None),\n            ) as mock_create_account:\n                with patch.object(\n                    self.account, \"_build_response\", return_value=\"build_response\"\n                ):\n                    self.assertEqual(\n                        self.account.create_account(content), \"build_response\"\n                    )\n                    mock_create_account.assert_called_once()\n\n    def test_002_create_account_msg_check_failure(self):\n        \"\"\"test create_account failure\"\"\"\n        content = {\"protected\": {}, \"payload\": {}}\n        with patch.object(self.account, \"message\") as mock_message:\n            mock_message.check.return_value = (400, \"error\", \"detail\", {}, {}, None)\n            with patch.object(\n                self.account,\n                \"_create_account\",\n                return_value=(200, \"test_account\", None),\n            ) as mock_create_account:\n                with patch.object(\n                    self.account, \"_build_response\", return_value=\"build_response\"\n                ):\n                    self.assertEqual(\n                        self.account.create_account(content), \"build_response\"\n                    )\n                    mock_create_account.assert_not_called()\n\n    def test_003_create_account_onlyreturnexisting(self):\n        \"\"\"test create_account onlyreturnexisting branch\"\"\"\n        content = {\"protected\": {}, \"payload\": {\"onlyreturnexisting\": True}}\n        with patch.object(self.account, \"message\") as mock_message:\n            mock_message.check.return_value = (\n                200,\n                None,\n                None,\n                {},\n                {\"onlyreturnexisting\": True},\n                None,\n            )\n            with patch.object(\n                self.account,\n                \"_onlyreturnexisting\",\n                return_value=(200, \"test_account\", None),\n            ) as mock_onlyreturnexisting:\n                with patch.object(\n                    self.account, \"_build_response\", return_value=\"build_response\"\n                ):\n                    self.assertEqual(\n                        self.account.create_account(content), \"build_response\"\n                    )\n                    mock_onlyreturnexisting.assert_called_once()\n\n    def test_004__validate_contact_missing(self):\n        \"\"\"test _validate_contact missing contact\"\"\"\n        code, message, detail = self.account._validate_contact([])\n        self.assertEqual(code, 400)\n        self.assertEqual(message, self.account.err_msg_dic[\"malformed\"])\n\n    def test_005__validate_contact_invalid(self):\n        \"\"\"test _validate_contact invalid contact\"\"\"\n        with patch(\"acme_srv.account.validate_email\", return_value=False):\n            code, message, detail = self.account._validate_contact([\"invalid@contact\"])\n            self.assertEqual(code, 400)\n            self.assertEqual(message, self.account.err_msg_dic[\"invalidcontact\"])\n\n    def test_006__validate_contact_valid(self):\n        \"\"\"test _validate_contact valid contact\"\"\"\n        with patch(\"acme_srv.account.validate_email\", return_value=True):\n            code, message, detail = self.account._validate_contact([\"valid@contact\"])\n            self.assertEqual(code, 200)\n            self.assertIsNone(message)\n\n    def test_007__check_tos_agreed(self):\n        \"\"\"test _check_tos agreed\"\"\"\n        content = {\"termsofserviceagreed\": True}\n        code, message, detail = self.account._check_tos(content)\n        self.assertEqual(code, 200)\n        self.assertIsNone(message)\n\n    def test_008__check_tos_not_agreed(self):\n        \"\"\"test _check_tos not agreed\"\"\"\n        content = {\"termsofserviceagreed\": False}\n        code, message, detail = self.account._check_tos(content)\n        self.assertEqual(code, 403)\n        self.assertEqual(message, self.account.err_msg_dic[\"useractionrequired\"])\n\n    def test_009__check_tos_missing(self):\n        \"\"\"test _check_tos missing flag\"\"\"\n        content = {}\n        code, message, detail = self.account._check_tos(content)\n        self.assertEqual(code, 403)\n        self.assertEqual(message, self.account.err_msg_dic[\"useractionrequired\"])\n\n    def test_010__add_account_to_db_success_new(self):\n        \"\"\"test _add_account_to_db success\"\"\"\n        account_data = MagicMock()\n        account_data.name = \"test_account\"\n        account_data.jwk = {}\n        account_data.contact = []\n        with patch.object(\n            self.account.repository, \"add_account\", return_value=(\"test_account\", True)\n        ):\n            code, message, detail = self.account._add_account_to_db(account_data)\n            self.assertEqual(code, 201)\n            self.assertEqual(message, \"test_account\")\n\n    def test_011__add_account_to_db_success_existing(self):\n        \"\"\"test _add_account_to_db success\"\"\"\n        account_data = MagicMock()\n        account_data.name = \"test_account\"\n        account_data.jwk = {}\n        account_data.contact = []\n        with patch.object(\n            self.account.repository, \"add_account\", return_value=(\"test_account\", False)\n        ):\n            code, message, detail = self.account._add_account_to_db(account_data)\n            self.assertEqual(code, 200)\n            self.assertEqual(message, \"test_account\")\n\n    def test_011__add_account_to_db_exception(self):\n        \"\"\"test _add_account_to_db exception\"\"\"\n        account_data = MagicMock()\n        account_data.name = \"test_account\"\n        account_data.jwk = {}\n        account_data.contact = []\n        with patch.object(\n            self.account.repository, \"add_account\", side_effect=Exception(\"DB error\")\n        ):\n            with self.assertRaises(Exception) as context:\n                with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                    code, message, detail = self.account._add_account_to_db(\n                        account_data\n                    )\n                    self.assertEqual(code, 500)\n                    self.assertEqual(\n                        message, self.account.err_msg_dic[\"serverinternal\"]\n                    )\n                self.assertIn(\"DB error\", str(context.exception))\n                self.assertIn(\"Database error while adding account\", log_cm.output[0])\n\n    def test_012__parse_query_valid(self):\n        \"\"\"test _parse_query valid account\"\"\"\n        with patch.object(\n            self.account,\n            \"_lookup_account_by_name\",\n            return_value={\n                \"status\": \"valid\",\n                \"jwk\": \"{}\",\n                \"contact\": \"[]\",\n                \"created_at\": \"2026-02-08\",\n            },\n        ):\n            data = self.account._parse_query(\"test_account\")\n            self.assertEqual(data[\"status\"], \"valid\")\n\n    def test_013__parse_query_invalid(self):\n        \"\"\"test _parse_query invalid account\"\"\"\n        with patch.object(self.account, \"_lookup_account_by_name\", return_value=None):\n            data = self.account._parse_query(\"test_account\")\n            self.assertEqual(data[\"status\"], \"invalid\")\n\n    def test_014__onlyreturnexisting_acc_lookup_success(self):\n        \"\"\"test _onlyreturnexisting success\"\"\"\n        protected = {\"jwk\": {}}\n        payload = {\"onlyreturnexisting\": True}\n        with patch.object(\n            self.account,\n            \"_lookup_account_by_field\",\n            return_value={\"name\": \"test_account\"},\n        ):\n            with patch.object(\n                self.account, \"_parse_query\", return_value={\"status\": \"valid\"}\n            ):\n                code, message, detail = self.account._onlyreturnexisting(\n                    protected, payload\n                )\n                self.assertEqual(code, 200)\n                self.assertEqual(message, \"test_account\")\n                self.assertEqual(detail, {\"status\": \"valid\"})\n\n    def test_014__onlyreturnexisting_acc_lookup_failed(self):\n        \"\"\"test _onlyreturnexisting success\"\"\"\n        protected = {\"jwk\": {}}\n        payload = {\"onlyreturnexisting\": True}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            with patch.object(\n                self.account, \"_parse_query\", return_value={\"status\": \"valid\"}\n            ):\n                code, message, detail = self.account._onlyreturnexisting(\n                    protected, payload\n                )\n                self.assertEqual(code, 400)\n                self.assertEqual(\n                    message, self.account.err_msg_dic[\"accountdoesnotexist\"]\n                )\n                self.assertFalse(detail)\n\n    def test_015__onlyreturnexisting_no_jwk(self):\n        \"\"\"test _onlyreturnexisting no jwk\"\"\"\n        protected = {}\n        payload = {\"onlyreturnexisting\": True}\n        code, message, detail = self.account._onlyreturnexisting(protected, payload)\n        self.assertEqual(code, 400)\n        self.assertEqual(message, self.account.err_msg_dic[\"malformed\"])\n\n    def test_016__onlyreturnexisting_false(self):\n        \"\"\"test _onlyreturnexisting onlyreturnexisting false\"\"\"\n        protected = {\"jwk\": {}}\n        payload = {\"onlyreturnexisting\": False}\n        code, message, detail = self.account._onlyreturnexisting(protected, payload)\n        self.assertEqual(code, 400)\n        self.assertEqual(message, self.account.err_msg_dic[\"useractionrequired\"])\n\n    def test_017__onlyreturnexisting_missing(self):\n        \"\"\"test _onlyreturnexisting missing flag\"\"\"\n        protected = {\"jwk\": {}}\n        payload = {}\n        code, message, detail = self.account._onlyreturnexisting(protected, payload)\n        self.assertEqual(code, 500)\n        self.assertEqual(message, self.account.err_msg_dic[\"serverinternal\"])\n\n    def test_018__handle_deactivation_success(self):\n        \"\"\"test _handle_deactivation success\"\"\"\n        payload = {\"status\": \"deactivated\"}\n        with patch.object(\n            self.account, \"_deactivate_account\", return_value=(200, None, None)\n        ):\n            result = self.account._handle_deactivation(\"test_account\", payload)\n            self.assertIn(\"data\", result)\n            self.assertEqual(result[\"code\"], 200)\n            self.assertEqual(result[\"data\"][\"status\"], \"deactivated\")\n\n    def test_018__handle_deactivation_fail(self):\n        \"\"\"test _handle_deactivation success\"\"\"\n        payload = {\"status\": \"deactivated\"}\n        with patch.object(\n            self.account,\n            \"_deactivate_account\",\n            return_value=(400, \"deact_message\", \"deact_detail\"),\n        ):\n            # with patch.object(self.account, \"_build_response\", return_value={\"data\": {}}):\n            result = self.account._handle_deactivation(\"test_account\", payload)\n            self.assertIn(\"data\", result)\n            self.assertEqual(result[\"data\"][\"status\"], 400)\n            self.assertEqual(result[\"data\"][\"type\"], \"deact_message\")\n            self.assertEqual(result[\"data\"][\"detail\"], \"deact_detail\")\n\n    def test_019__handle_deactivation_status_invalid(self):\n        \"\"\"test _handle_deactivation invalid status\"\"\"\n        payload = {\"status\": \"active\"}\n        with patch.object(self.account, \"_build_response\", return_value={\"data\": {}}):\n            result = self.account._handle_deactivation(\"test_account\", payload)\n            self.assertIn(\"data\", result)\n\n    def test_020__deactivate_account_success(self):\n        \"\"\"test _deactivate_account success\"\"\"\n        with patch.object(self.account.repository, \"update_account\", return_value=True):\n            code, message, detail = self.account._deactivate_account(\"test_account\")\n            self.assertEqual(code, 200)\n\n    def test_021__deactivate_account_failure(self):\n        \"\"\"test _deactivate_account failure\"\"\"\n        with patch.object(\n            self.account.repository, \"update_account\", return_value=False\n        ):\n            code, message, detail = self.account._deactivate_account(\"test_account\")\n            self.assertEqual(code, 400)\n\n    def test_022__deactivate_account_exception(self):\n        \"\"\"test _deactivate_account exception\"\"\"\n        with patch.object(\n            self.account.repository, \"update_account\", side_effect=Exception(\"DB error\")\n        ):\n            with self.assertRaises(Exception) as context:\n                with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                    code, message, detail = self.account._deactivate_account(\n                        \"test_account\"\n                    )\n                    self.assertEqual(code, 500)\n                    self.assertEqual(\n                        message, self.account.err_msg_dic[\"serverinternal\"]\n                    )\n                    self.assertIn(\"DB error\", str(context.exception))\n                    self.assertIn(\n                        \"Database error while deactivating account\", log_cm.output[0]\n                    )\n\n    def test_023__handle_contact_update_success(self):\n        \"\"\"test _handle_contact_update success\"\"\"\n        with patch.object(\n            self.account, \"_update_account_contacts\", return_value=(200, None, None)\n        ):\n            with patch.object(\n                self.account,\n                \"_lookup_account_by_name\",\n                return_value={\n                    \"status\": \"valid\",\n                    \"jwk\": \"{}\",\n                    \"contact\": \"[]\",\n                    \"created_at\": \"2026-02-08\",\n                },\n            ):\n                with patch.object(\n                    self.account,\n                    \"_build_account_info\",\n                    return_value={\"status\": \"valid\"},\n                ):\n                    with patch.object(\n                        self.account, \"_build_response\", return_value={\"data\": {}}\n                    ):\n                        result = self.account._handle_contact_update(\"test_account\", {})\n                        self.assertIn(\"data\", result)\n\n    def test_024__handle_contact_update_failure(self):\n        \"\"\"test _handle_contact_update failure\"\"\"\n        with patch.object(\n            self.account,\n            \"_update_account_contacts\",\n            return_value=(400, \"error\", \"detail\"),\n        ):\n            with patch.object(\n                self.account, \"_build_response\", return_value={\"data\": {}}\n            ):\n                result = self.account._handle_contact_update(\"test_account\", {})\n                self.assertIn(\"data\", result)\n\n    def test_025__update_account_contacts_validation_failes(self):\n        \"\"\"test _update_account_contacts does not call update_account if validation fails\"\"\"\n        with patch.object(self.account.repository, \"update_account\") as mock_update:\n            with patch.object(\n                self.account, \"_validate_contact\", return_value=(400, \"foo\", \"bar\")\n            ):\n                code, message, detail = self.account._update_account_contacts(\n                    \"test_account\", {\"contact\": []}\n                )\n                self.assertEqual(code, 400)\n                self.assertEqual(message, \"foo\")\n                self.assertEqual(detail, \"bar\")\n                mock_update.assert_not_called()\n\n    def test_025__update_account_contacts_success(self):\n        \"\"\"test _update_account_contacts success\"\"\"\n        self.account.repository.update_account.return_value = True\n        with patch.object(\n            self.account, \"_validate_contact\", return_value=(200, None, None)\n        ):\n            code, message, detail = self.account._update_account_contacts(\n                \"test_account\", {\"contact\": []}\n            )\n            self.assertEqual(code, 200)\n\n    def test_026__update_account_contacts_failure(self):\n        \"\"\"test _update_account_contacts failure\"\"\"\n        self.account.repository.update_account.return_value = False\n        with patch.object(\n            self.account, \"_validate_contact\", return_value=(200, None, None)\n        ):\n            code, message, detail = self.account._update_account_contacts(\n                \"test_account\", {\"contact\": []}\n            )\n            self.assertEqual(code, 400)\n\n    def test_027__update_account_contacts_exception(self):\n        \"\"\"test _update_account_contacts exception\"\"\"\n        self.account.repository.update_account.side_effect = Exception(\"DB error\")\n        with patch.object(\n            self.account, \"_validate_contact\", return_value=(200, None, None)\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                code, message, detail = self.account._update_account_contacts(\n                    \"test_account\", {\"contact\": []}\n                )\n                self.assertEqual(code, 500)\n                self.assertEqual(message, self.account.err_msg_dic[\"serverinternal\"])\n                self.assertIn(\n                    \"Database error while updating account contacts\", log_cm.output[0]\n                )\n\n    def test_028__handle_key_change_success(self):\n        \"\"\"test _handle_key_change success\"\"\"\n        with patch.object(self.account, \"message\") as mock_message:\n            mock_message.check.return_value = (200, None, None, {}, {}, None)\n            with patch.object(\n                self.account, \"_rollover_account_key\", return_value=(200, None, None)\n            ):\n                with patch.object(\n                    self.account, \"_build_response\", return_value={\"data\": {}}\n                ):\n                    result = self.account._handle_key_change(\"test_account\", {}, {})\n                    self.assertIn(\"data\", result)\n\n    def test_029__handle_key_change_failure(self):\n        \"\"\"test _handle_key_change failure\"\"\"\n        with patch.object(self.account, \"message\") as mock_message:\n            mock_message.check.return_value = (400, \"error\", \"detail\", {}, {}, None)\n            with patch.object(\n                self.account, \"_build_response\", return_value={\"data\": {}}\n            ):\n                result = self.account._handle_key_change(\"test_account\", {}, {})\n                self.assertIn(\"data\", result)\n\n    def test_030__rollover_account_key_validation_success(self):\n        \"\"\"test _rollover_account_key success\"\"\"\n        self.account.repository.update_account.return_value = True\n        with patch.object(\n            self.account, \"_validate_key_change\", return_value=(200, None, None)\n        ):\n            code, message, detail = self.account._rollover_account_key(\n                \"test_account\", {}, {\"jwk\": {\"foo\": \"bar\"}}, {}\n            )\n            self.assertEqual(code, 200)\n\n    def test_030__rollover_account_key_validation_failure(self):\n        \"\"\"test _rollover_account_key success\"\"\"\n        self.account.repository.update_account.return_value = True\n        with patch.object(\n            self.account,\n            \"_validate_key_change\",\n            return_value=(400, \"message\", \"detail\"),\n        ):\n            self.assertEqual(\n                (400, \"message\", \"detail\"),\n                self.account._rollover_account_key(\n                    \"test_account\", {}, {\"jwk\": {\"foo\": \"bar\"}}, {}\n                ),\n            )\n\n    def test_031__rollover_account_key_failure(self):\n        \"\"\"test _rollover_account_key failure\"\"\"\n        self.account.repository.update_account.return_value = False\n        with patch.object(\n            self.account, \"_validate_key_change\", return_value=(200, None, None)\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n                code, message, detail = self.account._rollover_account_key(\n                    \"test_account\", {}, {\"jwk\": {\"foo\": \"bar\"}}, {}\n                )\n                self.assertEqual(code, 500)\n                self.assertEqual(message, self.account.err_msg_dic[\"serverinternal\"])\n                self.assertIn(detail, \"Key rollover failed\")\n            self.assertIn(\n                \"ERROR:test_a2c:Key rollover failed for account: test_account\",\n                log_cm.output[0],\n            )\n\n    def test_032__rollover_account_key_exception(self):\n        \"\"\"test _rollover_account_key exception\"\"\"\n        self.account.repository.update_account.side_effect = Exception(\"DB error\")\n        with patch.object(\n            self.account, \"_validate_key_change\", return_value=(200, None, None)\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                code, message, detail = self.account._rollover_account_key(\n                    \"test_account\", {}, {}, {}\n                )\n                self.assertEqual(code, 500)\n                self.assertEqual(message, self.account.err_msg_dic[\"serverinternal\"])\n                self.assertIn(\n                    \"Database error while updating account key\", log_cm.output[0]\n                )\n\n    def test_033__validate_key_change_success(self):\n        \"\"\"test _validate_key_change success\"\"\"\n        protected = {\"url\": \"test\", \"kid\": \"kid\"}\n        inner_protected = {\"jwk\": {}, \"url\": \"test\"}\n        inner_payload = {\"account\": \"kid\"}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 200)\n\n    def test_034__validate_key_change_missing_jwk(self):\n        \"\"\"test _validate_key_change missing jwk\"\"\"\n        protected = {\"url\": \"test\", \"kid\": \"kid\"}\n        inner_protected = {\"url\": \"test\"}\n        inner_payload = {\"account\": \"kid\"}\n        code, message, detail = self.account._validate_key_change(\n            \"test_account\", protected, inner_protected, inner_payload\n        )\n        self.assertEqual(code, 400)\n\n    def test_035__validate_key_change_key_exists(self):\n        \"\"\"test _validate_key_change key exists\"\"\"\n        protected = {\"url\": \"test\", \"kid\": \"kid\"}\n        inner_protected = {\"jwk\": {}, \"url\": \"test\"}\n        inner_payload = {\"account\": \"kid\"}\n        with patch.object(\n            self.account,\n            \"_lookup_account_by_field\",\n            return_value={\"name\": \"test_account\"},\n        ):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 400)\n\n    def test_036__validate_key_change_url_mismatch(self):\n        \"\"\"test _validate_key_change url mismatch\"\"\"\n        protected = {\"url\": \"test\", \"kid\": \"kid\"}\n        inner_protected = {\"jwk\": {}, \"url\": \"other\"}\n        inner_payload = {\"account\": \"kid\"}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 400)\n\n    def test_037__validate_key_change_missing_url(self):\n        \"\"\"test _validate_key_change missing url\"\"\"\n        protected = {\"kid\": \"kid\"}\n        inner_protected = {\"jwk\": {}}\n        inner_payload = {\"account\": \"kid\"}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 400)\n\n    def test_038__validate_key_change_kid_account_mismatch(self):\n        \"\"\"test _validate_key_change kid/account mismatch\"\"\"\n        protected = {\"url\": \"test\", \"kid\": \"kid\"}\n        inner_protected = {\"jwk\": {}, \"url\": \"test\"}\n        inner_payload = {\"account\": \"other\"}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 400)\n\n    def test_039__validate_key_change_missing_kid_account(self):\n        \"\"\"test _validate_key_change missing kid/account\"\"\"\n        protected = {\"url\": \"test\"}\n        inner_protected = {\"jwk\": {}, \"url\": \"test\"}\n        inner_payload = {}\n        with patch.object(self.account, \"_lookup_account_by_field\", return_value=None):\n            code, message, detail = self.account._validate_key_change(\n                \"test_account\", protected, inner_protected, inner_payload\n            )\n            self.assertEqual(code, 400)\n\n    def test_040__load_configuration(self):\n        \"\"\"test _load_configuration covers all config branches and error handling\"\"\"\n        from acme_srv.account import Account\n\n        # Patch load_config to return a configparser-like mock\n        config_mock = MagicMock()\n        config_mock.getboolean.side_effect = lambda section, key, fallback=False: {\n            (\"Account\", \"inner_header_nonce_allow\"): True,\n            (\"Account\", \"ecc_only\"): True,\n            (\"Account\", \"tos_check_disable\"): True,\n            (\"Account\", \"contact_check_disable\"): True,\n        }.get((section, key), fallback)\n        config_mock.get.side_effect = lambda section, key, fallback=None: {\n            (\"Directory\", \"tos_url\"): \"http://tos.url\",\n            (\"Directory\", \"url_prefix\"): \"/prefix\",\n        }.get((section, key), fallback)\n        config_mock.__contains__.side_effect = lambda k: k in [\"EABhandler\"]\n        config_mock.__getitem__.side_effect = (\n            lambda k: {\"eab_handler_file\": \"handler.py\"} if k == \"EABhandler\" else {}\n        )\n\n        # Patch eab_handler_load to return a module with EABhandler\n        eab_handler_module = MagicMock()\n        eab_handler_module.EABhandler = \"EABhandlerClass\"\n\n        with patch(\"acme_srv.account.load_config\", return_value=config_mock), patch(\n            \"acme_srv.account.eab_handler_load\", return_value=eab_handler_module\n        ):\n            account = Account(False, \"http://tester.local\", self.logger)\n            account._load_configuration()\n            self.assertTrue(account.config.inner_header_nonce_allow)\n            self.assertTrue(account.config.ecc_only)\n            self.assertTrue(account.config.tos_check_disable)\n            self.assertTrue(account.config.contact_check_disable)\n            self.assertEqual(account.config.tos_url, \"http://tos.url\")\n            self.assertTrue(account.config.eab_check)\n            self.assertEqual(account.config.eab_handler, \"EABhandlerClass\")\n            self.assertTrue(account.config.path_dic[\"acct_path\"].startswith(\"/prefix\"))\n\n        # Test EABhandler config incomplete branch\n        config_mock2 = MagicMock()\n        config_mock2.getboolean.return_value = False\n        config_mock2.get.return_value = None\n        config_mock2.__contains__.side_effect = lambda k: k in [\"EABhandler\"]\n        config_mock2.__getitem__.side_effect = lambda k: {} if k == \"EABhandler\" else {}\n        with patch(\"acme_srv.account.load_config\", return_value=config_mock2), patch(\n            \"acme_srv.account.eab_handler_load\", return_value=None\n        ):\n            account = Account(False, \"http://tester.local\", self.logger)\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                account._load_configuration()\n                self.assertIn(\n                    \"EABHandler configuration incomplete\", \" \".join(log_cm.output)\n                )\n\n        # Test EABhandler load failure branch\n        config_mock3 = MagicMock()\n        config_mock3.getboolean.return_value = False\n        config_mock3.get.return_value = None\n        config_mock3.__contains__.side_effect = lambda k: k in [\"EABhandler\"]\n        config_mock3.__getitem__.side_effect = (\n            lambda k: {\"eab_handler_file\": \"handler.py\"} if k == \"EABhandler\" else {}\n        )\n        with patch(\"acme_srv.account.load_config\", return_value=config_mock3), patch(\n            \"acme_srv.account.eab_handler_load\", return_value=None\n        ):\n            account = Account(False, \"http://tester.local\", self.logger)\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                account._load_configuration()\n                self.assertIn(\n                    \"EABHandler could not get loaded\", \" \".join(log_cm.output)\n                )\n\n    def test_041_load_configuration_without_accountsection(self):\n        from acme_srv.account import Account\n\n        config_mock = MagicMock()\n        config_mock.getboolean.side_effect = lambda section, key, fallback=False: {\n            (\"CAhandler\", \"foo\"): \"bar\",\n        }.get((section, key), fallback)\n\n        # Patch eab_handler_load to return a module with EABhandler\n        eab_handler_module = MagicMock()\n        eab_handler_module.EABhandler = \"EABhandlerClass\"\n\n        with patch(\"acme_srv.account.load_config\", return_value=config_mock), patch(\n            \"acme_srv.account.eab_handler_load\", return_value=eab_handler_module\n        ):\n            account = Account(False, \"http://tester.local\", self.logger)\n            account._load_configuration()\n            self.assertFalse(\n                account.config.tos_check_disable\n            )  # Default value should be used\n            self.assertFalse(\n                account.config.inner_header_nonce_allow\n            )  # Default value should be used\n            self.assertFalse(account.config.eab_check)  # Default value should be used\n\n    def test_041__create_account_success(self):\n        \"\"\"test _create_account success (all checks pass, EAB off)\"\"\"\n        self.account.config.tos_url = None\n        self.account.config.tos_check_disable = False\n        self.account.config.eab_check = False\n        self.account.config.contact_check_disable = False\n        payload = {\"contact\": [\"test@example.com\"]}\n        protected = {\"alg\": \"RS256\", \"jwk\": {\"kty\": \"RSA\", \"n\": \"abc\", \"e\": \"AQAB\"}}\n        with patch.object(\n            self.account, \"_validate_contact\", return_value=(200, None, None)\n        ), patch.object(\n            self.account, \"_add_account_to_db\", return_value=(201, \"test_account\", None)\n        ) as mock_add_db:\n            code, message, detail = self.account._create_account(payload, protected)\n            self.assertEqual(code, 201)\n            self.assertEqual(message, \"test_account\")\n            mock_add_db.assert_called_once()\n\n    def test_042__create_account_tos_check_fail(self):\n        \"\"\"test _create_account fails TOS check\"\"\"\n        self.account.config.tos_url = \"http://tos.url\"\n        self.account.config.tos_check_disable = False\n        self.account.config.eab_check = False\n        payload = {\"contact\": [\"test@example.com\"]}\n        protected = {\"alg\": \"RS256\", \"jwk\": {}}\n        with patch.object(\n            self.account, \"_check_tos\", return_value=(403, \"tos_error\", \"tos_detail\")\n        ):\n            code, message, detail = self.account._create_account(payload, protected)\n            self.assertEqual(code, 403)\n            self.assertEqual(message, \"tos_error\")\n\n    def test_043__create_account_eab_check_fail(self):\n        \"\"\"test _create_account fails EAB check\"\"\"\n        self.account.config.tos_url = None\n        self.account.config.tos_check_disable = False\n        self.account.config.eab_check = True\n        self.account.config.eab_handler = MagicMock()\n        payload = {\"contact\": [\"test@example.com\"]}\n        protected = {\"alg\": \"RS256\", \"jwk\": {}}\n        with patch(\"acme_srv.account.ExternalAccountBinding\") as mock_eab:\n            mock_eab.return_value.check.return_value = (403, \"eab_error\", \"eab_detail\")\n            code, message, detail = self.account._create_account(payload, protected)\n            self.assertEqual(code, 403)\n            self.assertEqual(message, \"eab_error\")\n\n    def test_044__create_account_contact_check_fail(self):\n        \"\"\"test _create_account fails contact validation\"\"\"\n        self.account.config.tos_url = None\n        self.account.config.tos_check_disable = False\n        self.account.config.eab_check = False\n        self.account.config.contact_check_disable = False\n        payload = {\"contact\": [\"bad@example.com\"]}\n        protected = {\"alg\": \"RS256\", \"jwk\": {}}\n        with patch.object(\n            self.account,\n            \"_validate_contact\",\n            return_value=(400, \"contact_error\", \"contact_detail\"),\n        ):\n            code, message, detail = self.account._create_account(payload, protected)\n            self.assertEqual(code, 400)\n            self.assertEqual(message, \"contact_error\")\n\n    def test_045__create_account_eab_kid_set(self):\n        \"\"\"test _create_account sets eab_kid if present\"\"\"\n        self.account.config.tos_url = None\n        self.account.config.tos_check_disable = False\n        self.account.config.eab_check = True\n        self.account.config.eab_handler = MagicMock()\n        payload = {\n            \"contact\": [\"test@example.com\"],\n            \"externalaccountbinding\": {\"protected\": \"protectedval\"},\n        }\n        protected = {\"alg\": \"RS256\", \"jwk\": {}}\n        with patch(\"acme_srv.account.ExternalAccountBinding\") as mock_eab, patch.object(\n            self.account, \"_validate_contact\", return_value=(200, None, None)\n        ), patch.object(\n            self.account, \"_add_account_to_db\", return_value=(201, \"test_account\", None)\n        ) as mock_add_db:\n            mock_eab.return_value.check.return_value = (200, None, None)\n            mock_eab.return_value.get_kid.return_value = \"eabkid123\"\n            code, message, detail = self.account._create_account(payload, protected)\n            self.assertEqual(code, 201)\n            self.assertEqual(message, \"test_account\")\n            mock_add_db.assert_called_once()\n            # Check that eab_kid was set in the AccountData passed to _add_account_to_db\n            args, kwargs = mock_add_db.call_args\n            self.assertEqual(args[0].eab_kid, \"eabkid123\")\n\n    def test_046__handle_key_change_success(self):\n        \"\"\"test _handle_key_change success path (code==200)\"\"\"\n        account_name = \"test_account\"\n        payload = {\"foo\": \"bar\"}\n        protected = {\"url\": \"key-change/123\"}\n        with patch.object(self.account, \"message\") as mock_message, patch.object(\n            self.account, \"_rollover_account_key\", return_value=(200, None, None)\n        ) as mock_rollover, patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            mock_message.check.return_value = (\n                200,\n                None,\n                None,\n                {\"jwk\": {}},\n                {\"account\": \"acc\"},\n                None,\n            )\n            result = self.account._handle_key_change(account_name, payload, protected)\n            self.assertIn(\"data\", result)\n            mock_rollover.assert_called_once()\n            mock_build_response.assert_called_once()\n\n    def test_047__handle_key_change_check_fail(self):\n        \"\"\"test _handle_key_change when message.check returns code!=200\"\"\"\n        account_name = \"test_account\"\n        payload = {\"foo\": \"bar\"}\n        protected = {\"url\": \"key-change/123\"}\n        with patch.object(self.account, \"message\") as mock_message, patch.object(\n            self.account, \"_rollover_account_key\"\n        ) as mock_rollover, patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            mock_message.check.return_value = (400, \"err\", \"fail\", {}, {}, None)\n            result = self.account._handle_key_change(account_name, payload, protected)\n            self.assertIn(\"data\", result)\n            mock_rollover.assert_not_called()\n            mock_build_response.assert_called_once()\n\n    def test_048__handle_key_change_rollover_fail(self):\n        \"\"\"test _handle_key_change when rollover returns code!=200\"\"\"\n        account_name = \"test_account\"\n        payload = {\"foo\": \"bar\"}\n        protected = {\"url\": \"key-change/123\"}\n        with patch.object(self.account, \"message\") as mock_message, patch.object(\n            self.account, \"_rollover_account_key\", return_value=(500, \"err\", \"fail\")\n        ) as mock_rollover, patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            mock_message.check.return_value = (\n                200,\n                None,\n                None,\n                {\"jwk\": {}},\n                {\"account\": \"acc\"},\n                None,\n            )\n            result = self.account._handle_key_change(account_name, payload, protected)\n            self.assertIn(\"data\", result)\n            mock_rollover.assert_called_once()\n            mock_build_response.assert_called_once()\n\n    def test_049__handle_key_change_url_missing(self):\n        \"\"\"test _handle_key_change with missing url in protected\"\"\"\n        account_name = \"test_account\"\n        payload = {\"foo\": \"bar\"}\n        protected = {\"noturl\": \"nope\"}\n        with patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            result = self.account._handle_key_change(account_name, payload, protected)\n            self.assertIn(\"data\", result)\n            mock_build_response.assert_called_once()\n\n    def test_050__handle_account_query_valid(self):\n        \"\"\"test _handle_account_query with valid account\"\"\"\n        account_name = \"test_account\"\n        account_obj = {\n            \"status\": \"valid\",\n            \"jwk\": \"{}\",\n            \"contact\": \"[]\",\n            \"created_at\": \"2026-02-08\",\n        }\n        with patch.object(\n            self.account, \"_lookup_account_by_name\", return_value=account_obj\n        ), patch.object(\n            self.account, \"_build_account_info\", return_value={\"status\": \"valid\"}\n        ), patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            result = self.account._handle_account_query(account_name)\n            self.assertIn(\"data\", result)\n            mock_build_response.assert_called_once()\n\n    def test_051__handle_account_query_invalid(self):\n        \"\"\"test _handle_account_query with invalid account (not found)\"\"\"\n        account_name = \"test_account\"\n        with patch.object(\n            self.account, \"_lookup_account_by_name\", return_value=None\n        ), patch.object(\n            self.account, \"_build_account_info\", return_value={\"status\": \"valid\"}\n        ) as mock_build_account_info, patch.object(\n            self.account, \"_build_response\", return_value={\"data\": {}}\n        ) as mock_build_response:\n            result = self.account._handle_account_query(account_name)\n            self.assertIn(\"data\", result)\n            mock_build_response.assert_called_once()\n            mock_build_account_info.assert_not_called()\n\n    def test_052__lookup_account_by_name_success(self):\n        \"\"\"test _lookup_account_by_name returns account on success\"\"\"\n        with patch.object(\n            self.account.repository,\n            \"lookup_account\",\n            return_value={\"name\": \"test_account\"},\n        ) as mock_lookup:\n            result = self.account._lookup_account_by_name(\"test_account\")\n            self.assertEqual(result, {\"name\": \"test_account\"})\n            mock_lookup.assert_called_once_with(\"name\", \"test_account\")\n\n    def test_053__lookup_account_by_name_exception(self):\n        \"\"\"test _lookup_account_by_name returns None on AccountDatabaseError\"\"\"\n        with patch.object(\n            self.account.repository, \"lookup_account\", side_effect=Exception(\"DB error\")\n        ) as mock_lookup:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                result = self.account._lookup_account_by_name(\"test_account\")\n                self.assertIsNone(result)\n                self.assertIn(\n                    \"Database error during account lookup\", \" \".join(log_cm.output)\n                )\n\n    def test_052__lookup_account_by_field_success(self):\n        \"\"\"test _lookup_account_by_name returns account on success\"\"\"\n        with patch.object(\n            self.account.repository,\n            \"lookup_account\",\n            return_value={\"name\": \"test_account\"},\n        ) as mock_lookup:\n            result = self.account._lookup_account_by_field(\"field\", \"value\")\n            self.assertEqual(result, {\"name\": \"test_account\"})\n            mock_lookup.assert_called_once_with(\"value\", \"field\")\n\n    def test_053__lookup_account_by_field_exception(self):\n        \"\"\"test _lookup_account_by_name returns None on AccountDatabaseError\"\"\"\n        with patch.object(\n            self.account.repository, \"lookup_account\", side_effect=Exception(\"DB error\")\n        ) as mock_lookup:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                result = self.account._lookup_account_by_field(\"field\", \"value\")\n                self.assertIsNone(result)\n                self.assertIn(\n                    \"Database error during account lookup\", \" \".join(log_cm.output)\n                )\n\n    def test_056__build_account_info_normal(self):\n        \"\"\"test _build_account_info with all fields present\"\"\"\n        account_obj = {\n            \"status\": \"valid\",\n            \"jwk\": '{\"kty\": \"RSA\", \"n\": \"abc\", \"e\": \"AQAB\"}',\n            \"contact\": '[\"mailto:test@example.com\"]',\n            \"created_at\": \"2026-02-08 12:00:00\",\n        }\n        with patch(\"acme_srv.account.date_to_datestr\", return_value=\"date_str\"):\n            result = self.account._build_account_info(account_obj)\n            self.assertEqual(result[\"status\"], \"valid\")\n            self.assertEqual(result[\"key\"], {\"kty\": \"RSA\", \"n\": \"abc\", \"e\": \"AQAB\"})\n            self.assertEqual(result[\"contact\"], [\"mailto:test@example.com\"])\n            self.assertEqual(result[\"createdAt\"], \"date_str\")\n\n    def test_056__build_account_info_witheab(self):\n        \"\"\"test _build_account_info with all fields present\"\"\"\n        account_obj = {\n            \"status\": \"valid\",\n            \"jwk\": '{\"kty\": \"RSA\", \"n\": \"abc\", \"e\": \"AQAB\"}',\n            \"contact\": '[\"mailto:test@example.com\"]',\n            \"created_at\": \"2026-02-08 12:00:00\",\n            \"eab_kid\": \"kid123\",\n        }\n        with patch(\"acme_srv.account.date_to_datestr\", return_value=\"date_str\"):\n            result = self.account._build_account_info(account_obj)\n            self.assertEqual(result[\"status\"], \"valid\")\n            self.assertEqual(result[\"key\"], {\"kty\": \"RSA\", \"n\": \"abc\", \"e\": \"AQAB\"})\n            self.assertEqual(result[\"contact\"], [\"mailto:test@example.com\"])\n            self.assertEqual(result[\"createdAt\"], \"date_str\")\n            self.assertEqual(result[\"eab_kid\"], \"kid123\")\n\n    def test_057__build_account_info_missing_fields(self):\n        \"\"\"test _build_account_info with missing optional fields\"\"\"\n        account_obj = {\n            \"jwk\": \"{}\",\n            \"contact\": \"[]\",\n            \"created_at\": \"2026-02-08 12:00:00\",\n        }\n        with patch(\"acme_srv.account.date_to_datestr\", return_value=\"date_str\"):\n            result = self.account._build_account_info(account_obj)\n            self.assertEqual(result[\"status\"], \"valid\")  # default\n            self.assertEqual(result[\"key\"], {})\n            self.assertEqual(result[\"contact\"], [])\n            self.assertEqual(result[\"createdAt\"], \"date_str\")\n            self.assertNotIn(\"eab_kid\", result)\n\n    def test_058__build_account_info_eab_kid_empty(self):\n        \"\"\"test _build_account_info with eab_kid present but empty\"\"\"\n        account_obj = {\n            \"status\": \"valid\",\n            \"jwk\": \"{}\",\n            \"contact\": \"[]\",\n            \"created_at\": \"2026-02-08 12:00:00\",\n            \"eab_kid\": \"\",\n        }\n        result = self.account._build_account_info(account_obj)\n        self.assertNotIn(\"eab_kid\", result)\n\n    def test_059__build_response_201(self):\n        \"\"\"test _build_response for code 201 (account creation)\"\"\"\n        code = 201\n        message = \"test_account\"\n        detail = None\n        payload = {\"contact\": [\"mailto:test@example.com\"]}\n        self.account.server_name = \"http://tester.local\"\n        self.account.config.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        with patch.object(\n            self.account.message,\n            \"prepare_response\",\n            return_value={\"data\": {\"status\": \"valid\"}, \"header\": {}},\n        ) as mock_prepare:\n            result = self.account._build_response(code, message, detail, payload)\n            self.assertIn(\"data\", result)\n            self.assertIn(\"header\", result)\n            mock_prepare.assert_called_once()\n\n    def test_060__build_response_200(self):\n        \"\"\"test _build_response for code 200 (success, detail contains status)\"\"\"\n        code = 200\n        message = \"test_account\"\n        detail = {\"status\": \"valid\"}\n        payload = {}\n        self.account.server_name = \"http://tester.local\"\n        self.account.config.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        with patch.object(\n            self.account.message,\n            \"prepare_response\",\n            return_value={\"data\": {\"status\": \"valid\"}, \"header\": {}},\n        ) as mock_prepare:\n            result = self.account._build_response(code, message, detail, payload)\n            self.assertIn(\"data\", result)\n            self.assertIn(\"header\", result)\n            mock_prepare.assert_called_once()\n\n    def test_061__build_response_error(self):\n        \"\"\"test _build_response for error code (e.g. 400)\"\"\"\n        code = 400\n        message = \"error\"\n        detail = \"tosfalse\"\n        payload = {}\n        with patch.object(\n            self.account.message, \"prepare_response\", return_value={\"error\": \"error\"}\n        ) as mock_prepare:\n            result = self.account._build_response(code, message, detail, payload)\n            self.assertIn(\"error\", result)\n            mock_prepare.assert_called_once()\n\n    def test_062__build_response_eab_binding(self):\n        \"\"\"test _build_response with eab_check and externalaccountbinding in payload\"\"\"\n        code = 201\n        message = \"test_account\"\n        detail = None\n        payload = {\n            \"contact\": [\"mailto:test@example.com\"],\n            \"externalaccountbinding\": {\"foo\": \"bar\"},\n        }\n        self.account.server_name = \"http://tester.local\"\n        self.account.config.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        self.account.config.eab_check = True\n        with patch.object(\n            self.account.message,\n            \"prepare_response\",\n            return_value={\n                \"data\": {\"status\": \"valid\", \"externalaccountbinding\": {\"foo\": \"bar\"}},\n                \"header\": {},\n            },\n        ) as mock_prepare:\n            result = self.account._build_response(code, message, detail, payload)\n            self.assertIn(\"data\", result)\n            self.assertIn(\"externalaccountbinding\", result[\"data\"])\n            mock_prepare.assert_called_once()\n\n    def test_063_parse_request_error(self):\n        \"\"\"test parse_request returns error response when message.check fails\"\"\"\n        content = {\"foo\": \"bar\"}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(400, \"error\", \"fail\", {}, {}, None),\n        ), patch.object(\n            self.account, \"_build_response\", return_value={\"error\": \"fail\"}\n        ) as mock_build_response:\n            result = self.account.parse_request(content)\n            self.assertEqual(result, {\"error\": \"fail\"})\n            mock_build_response.assert_called_once()\n\n    def test_064_parse_request_deactivation(self):\n        \"\"\"test parse_request handles deactivation branch\"\"\"\n        content = {\"foo\": \"bar\"}\n        payload = {\"status\": \"deactivated\"}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(200, None, None, {}, payload, \"test_account\"),\n        ), patch.object(\n            self.account,\n            \"_handle_deactivation\",\n            return_value={\"data\": {\"status\": \"deactivated\"}},\n        ) as mock_handle:\n            result = self.account.parse_request(content)\n            self.assertIn(\"data\", result)\n            mock_handle.assert_called_once_with(\"test_account\", payload)\n\n    def test_065_parse_request_contact_update(self):\n        \"\"\"test parse_request handles contact update branch\"\"\"\n        content = {\"foo\": \"bar\"}\n        payload = {\"contact\": [\"mailto:test@example.com\"]}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(200, None, None, {}, payload, \"test_account\"),\n        ), patch.object(\n            self.account,\n            \"_handle_contact_update\",\n            return_value={\"data\": {\"contact\": [\"mailto:test@example.com\"]}},\n        ) as mock_handle:\n            result = self.account.parse_request(content)\n            self.assertIn(\"data\", result)\n            mock_handle.assert_called_once_with(\"test_account\", payload)\n\n    def test_066_parse_request_key_change(self):\n        \"\"\"test parse_request handles key change branch\"\"\"\n        content = {\"foo\": \"bar\"}\n        payload = {\"payload\": {}}\n        protected = {\"protected\": {}}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(200, None, None, protected, payload, \"test_account\"),\n        ), patch.object(\n            self.account,\n            \"_handle_key_change\",\n            return_value={\"data\": {\"keychange\": True}},\n        ) as mock_handle:\n            result = self.account.parse_request(content)\n            self.assertIn(\"data\", result)\n            mock_handle.assert_called_once_with(\"test_account\", payload, protected)\n\n    def test_067_parse_request_account_query(self):\n        \"\"\"test parse_request handles account query branch (empty payload)\"\"\"\n        content = {\"foo\": \"bar\"}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(200, None, None, {}, {}, \"test_account\"),\n        ), patch.object(\n            self.account,\n            \"_handle_account_query\",\n            return_value={\"data\": {\"status\": \"valid\"}},\n        ) as mock_handle:\n            result = self.account.parse_request(content)\n            self.assertIn(\"data\", result)\n            mock_handle.assert_called_once_with(\"test_account\")\n\n    def test_068_parse_request_unknown(self):\n        \"\"\"test parse_request handles unknown request branch\"\"\"\n        content = {\"foo\": \"bar\"}\n        payload = {\"unknown\": True}\n        with patch.object(\n            self.account.message,\n            \"check\",\n            return_value=(200, None, None, {}, payload, \"test_account\"),\n        ), patch.object(\n            self.account, \"_build_response\", return_value={\"error\": \"Unknown request\"}\n        ) as mock_build_response:\n            result = self.account.parse_request(content)\n            self.assertEqual(result, {\"error\": \"Unknown request\"})\n            mock_build_response.assert_called_once_with(\n                400, self.account.err_msg_dic[\"malformed\"], \"Unknown request\"\n            )\n\n    def test_069_new_calls_create_account(self):\n        \"\"\"test new() calls create_account and returns its result\"\"\"\n        content = {\"foo\": \"bar\"}\n        with patch.object(\n            self.account, \"create_account\", return_value={\"data\": {\"status\": \"valid\"}}\n        ) as mock_create:\n            result = self.account.new(content)\n            self.assertEqual(result, {\"data\": {\"status\": \"valid\"}})\n            mock_create.assert_called_once_with(content)\n\n    def test_070_parse_calls_parse_request(self):\n        \"\"\"test parse() calls parse_request and returns its result\"\"\"\n        content = {\"foo\": \"bar\"}\n        with patch.object(\n            self.account, \"parse_request\", return_value={\"data\": {\"status\": \"valid\"}}\n        ) as mock_parse_request:\n            result = self.account.parse(content)\n            self.assertEqual(result, {\"data\": {\"status\": \"valid\"}})\n            mock_parse_request.assert_called_once_with(content)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_acme_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, R0913, W0212\n\nimport sys\nimport os\nimport josepy\nimport unittest\nfrom unittest.mock import patch, mock_open, Mock, MagicMock\nimport configparser\nimport josepy\nimport json\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.backends import default_backend\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n        from examples.ca_handler.acme_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        pass\n\n    def _generate_full_jwk(self):\n        \"\"\"Helper to generate a full josepy.JWKRSA object\"\"\"\n        private_key = rsa.generate_private_key(\n            public_exponent=65537, key_size=2048, backend=default_backend()\n        )\n        return josepy.JWKRSA(key=private_key)\n\n    def test_214__order_authorization_unexpected_status(self):\n        \"\"\"CAhandler._order_authorization() - unexpected status branch\"\"\"\n        cah = self.cahandler\n        acmeclient = Mock()\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        authzr.body.status = \"foobar\"\n        order.authorizations = [authzr]\n        user_key = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            result = cah._order_authorization(acmeclient, order, user_key)\n        self.assertFalse(result)\n        self.assertIn(\"authorization in unexpected state: foobar\", \" \".join(lcm.output))\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_200__synchronize_profiles_success(self, mock_url_get):\n        \"\"\"CAhandler._synchronize_profiles() - success path\"\"\"\n        from examples.ca_handler.acme_ca_handler import CAhandler\n\n        cah = self.cahandler\n        mock_url_get.return_value = (\n            json.dumps({\"meta\": {\"profiles\": {\"foo\": \"bar\"}}}),\n            200,\n            None,\n        )\n        repository = MagicMock()\n        cah._synchronize_profiles(repository, \"http://acme\", 123456)\n        self.assertTrue(repository.profile_list_set.called)\n        args = repository.profile_list_set.call_args[0][0]\n        self.assertIn(\"profiles\", args[\"value\"])\n        self.assertIn(\"synchronized_at\", args[\"value\"])\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_201__synchronize_profiles_error(self, mock_url_get):\n        \"\"\"CAhandler._synchronize_profiles() - error path\"\"\"\n        from examples.ca_handler.acme_ca_handler import CAhandler\n\n        cah = self.cahandler\n        mock_url_get.return_value = (\"fail\", 500, \"error\")\n        repository = MagicMock()\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            cah._synchronize_profiles(repository, \"http://acme\", 123456)\n        self.assertIn(\"Error during profile synchronization\", \" \".join(lcm.output))\n\n    @patch(\"examples.ca_handler.acme_ca_handler.Thread\")\n    @patch(\"examples.ca_handler.acme_ca_handler.uts_now\", return_value=1000)\n    def test_202_load_profiles_outdated_sync(self, mock_uts, mock_thread):\n        \"\"\"CAhandler.synchronize_profiles() - outdated, sync mode\"\"\"\n        cah = self.cahandler\n        repository = MagicMock()\n        repository.profile_list_get.return_value = {\"synchronized_at\": 0}\n        cah._synchronize_profiles = MagicMock()\n        thread_instance = MagicMock()\n        mock_thread.return_value = thread_instance\n        cah.synchronize_profiles(repository, \"http://acme\", 100, async_mode=False)\n        self.assertTrue(thread_instance.start.called)\n        self.assertTrue(thread_instance.join.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.Thread\")\n    @patch(\"examples.ca_handler.acme_ca_handler.uts_now\", return_value=1000)\n    def test_203_load_profiles_outdated_async(self, mock_uts, mock_thread):\n        \"\"\"CAhandler.synchronize_profiles() - outdated, async mode\"\"\"\n        cah = self.cahandler\n        repository = MagicMock()\n        repository.profile_list_get.return_value = {\"synchronized_at\": 0}\n        cah._synchronize_profiles = MagicMock()\n        thread_instance = MagicMock()\n        mock_thread.return_value = thread_instance\n        cah.synchronize_profiles(repository, \"http://acme\", 100, async_mode=True)\n        self.assertTrue(thread_instance.start.called)\n        self.assertFalse(thread_instance.join.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.Thread\")\n    @patch(\"examples.ca_handler.acme_ca_handler.uts_now\", return_value=1000)\n    def test_204_load_profiles_up_to_date(self, mock_uts, mock_thread):\n        \"\"\"CAhandler.synchronize_profiles() - up-to-date profiles\"\"\"\n        cah = self.cahandler\n        repository = MagicMock()\n        repository.profile_list_get.return_value = {\n            \"synchronized_at\": 2000,\n            \"profiles\": {\"foo\": \"bar\"},\n        }\n        cah._synchronize_profiles = MagicMock()\n        profiles = cah.synchronize_profiles(\n            repository, \"http://acme\", 100, async_mode=False\n        )\n        self.assertEqual(profiles, {\"foo\": \"bar\"})\n        self.assertFalse(mock_thread.return_value.start.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_205__get_renewalinfo_endpoint_url_success(self, mock_url_get):\n        \"\"\"CAhandler._get_renewalinfo_endpoint_url() - directory has renewalInfo\"\"\"\n        cah = self.cahandler\n        directory_json = json.dumps({\"renewalInfo\": \"http://acme/renewal-info\"})\n        mock_url_get.return_value = (directory_json, 200)\n        url = cah._get_renewalinfo_endpoint_url(\"http://acme\")\n        self.assertEqual(url, \"http://acme/renewal-info\")\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_206__get_renewalinfo_endpoint_url_no_renewalinfo(self, mock_url_get):\n        \"\"\"CAhandler._get_renewalinfo_endpoint_url() - directory missing renewalInfo\"\"\"\n        cah = self.cahandler\n        directory_json = json.dumps({\"foo\": \"bar\"})\n        mock_url_get.return_value = (directory_json, 200)\n        url = cah._get_renewalinfo_endpoint_url(\"http://acme\")\n        self.assertEqual(url, \"http://acme/renewal-info\")\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_207__get_renewalinfo_endpoint_url_json_error(self, mock_url_get):\n        \"\"\"CAhandler._get_renewalinfo_endpoint_url() - JSON decode error\"\"\"\n        cah = self.cahandler\n        mock_url_get.return_value = (\"notjson\", 200)\n        url = cah._get_renewalinfo_endpoint_url(\"http://acme\")\n        self.assertEqual(url, \"http://acme/renewal-info\")\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_208__get_renewalinfo_endpoint_url_fetch_error(self, mock_url_get):\n        \"\"\"CAhandler._get_renewalinfo_endpoint_url() - fetch error\"\"\"\n        cah = self.cahandler\n        mock_url_get.return_value = (\"fail\", 500)\n        url = cah._get_renewalinfo_endpoint_url(\"http://acme\")\n        self.assertEqual(url, \"http://acme/renewal-info\")\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\", side_effect=Exception(\"fail\"))\n    def test_209__get_renewalinfo_endpoint_url_exception(self, mock_url_get):\n        \"\"\"CAhandler._get_renewalinfo_endpoint_url() - exception\"\"\"\n        cah = self.cahandler\n        url = cah._get_renewalinfo_endpoint_url(\"http://acme\")\n        self.assertEqual(url, \"http://acme/renewal-info\")\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_210_lookup_renewalinfo_success(self, mock_url_get):\n        \"\"\"CAhandler.lookup_renewalinfo() - success\"\"\"\n        cah = self.cahandler\n        renewalinfo_json = json.dumps({\"cert\": \"foo\", \"csr\": \"bar\"})\n        mock_url_get.return_value = (renewalinfo_json, 200)\n        code, dic = cah.lookup_renewalinfo(\"http://acme\", \"abc123\")\n        self.assertEqual(code, 200)\n        self.assertEqual(dic, {\"cert\": \"foo\", \"csr\": \"bar\"})\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_211_lookup_renewalinfo_json_error(self, mock_url_get):\n        \"\"\"CAhandler.lookup_renewalinfo() - JSON decode error\"\"\"\n        cah = self.cahandler\n        mock_url_get.return_value = (\"notjson\", 200)\n        code, dic = cah.lookup_renewalinfo(\"http://acme\", \"abc123\")\n        self.assertEqual(code, 500)\n        self.assertEqual(dic, {})\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\")\n    def test_212_lookup_renewalinfo_unexpected_response(self, mock_url_get):\n        \"\"\"CAhandler.lookup_renewalinfo() - unexpected response\"\"\"\n        cah = self.cahandler\n        mock_url_get.return_value = \"fail\"\n        code, dic = cah.lookup_renewalinfo(\"http://acme\", \"abc123\")\n        self.assertEqual(code, 500)\n        self.assertEqual(dic, {})\n\n    @patch(\"examples.ca_handler.acme_ca_handler.url_get\", side_effect=Exception(\"fail\"))\n    def test_213_lookup_renewalinfo_exception(self, mock_url_get):\n        \"\"\"CAhandler.lookup_renewalinfo() - exception\"\"\"\n        cah = self.cahandler\n        code, dic = cah.lookup_renewalinfo(\"http://acme\", \"abc123\")\n        self.assertEqual(code, 400)\n        self.assertEqual(dic, {})\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n        from examples.ca_handler.acme_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        pass\n\n    def _generate_full_jwk(self):\n        \"\"\"Helper to generate a full josepy.JWKRSA object\"\"\"\n        private_key = rsa.generate_private_key(\n            public_exponent=65537, key_size=2048, backend=default_backend()\n        )\n        return josepy.JWKRSA(key=private_key)\n\n    def test_001___init__(self):\n        \"\"\"init\"\"\"\n        self.assertTrue(self.cahandler.__enter__())\n\n    def test_002___exit__(self):\n        \"\"\"exit\"\"\"\n        self.assertFalse(self.cahandler.__exit__())\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_003__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load default configparser object\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"CAhandler\" section is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_004__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_005__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load unknown values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_006__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load key_file value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"acme_keyfile\": \"key_file\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"key_file\", self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_007__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load key_file value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"acme_keyfile\": \"key_file\",\n            \"acme_keypath\": \"acme_keypath\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"key_file\", self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertEqual(\"acme_keypath\", self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_008__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load url value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"acme_url\": \"url\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertEqual(\"url\", self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_009__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load account values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"acme_account\": \"acme_account\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertEqual(\"acme_account\", self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_010__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load key_size\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"acme_account_keysize\": \"acme_account_keysize\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(\"acme_account_keysize\", self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_011__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load email\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"acme_account_email\": \"acme_account_email\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"directory_path\": \"/directory\", \"acct_path\": \"/acme/acct/\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertEqual(\"acme_account_email\", self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_012__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load email\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"directory_path\": \"directory_path\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"directory_path\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_013__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load email\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"account_path\": \"account_path\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"account_path\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_014__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"foo\", \"bar\"]'}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertEqual([\"foo\", \"bar\"], self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_015__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertEqual(\"failed to parse\", self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load allowed_domainlist from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_016__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ssl_verify\": False}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertFalse(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_017__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ssl_verify\": True}\n\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_018__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ssl_verify\": \"aaa\"}\n\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse ssl_verify parameter: Not a boolean: aaa\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_019__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"eab_kid\": \"eab_kid\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"eab_kid\", self.cahandler.eab_kid)\n        self.assertFalse(self.cahandler.eab_hmac_key)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.load_config\")\n    def test_020__config_load(self, mock_load_cfg):\n        \"\"\"test _config_load allowlist - failed json parse\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"eab_hmac_key\": \"eab_hmac_key\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.acme_keyfile)\n        self.assertFalse(self.cahandler.acme_url)\n        self.assertFalse(self.cahandler.account)\n        self.assertEqual(\n            {\"acct_path\": \"/acme/acct/\", \"directory_path\": \"/directory\"},\n            self.cahandler.path_dic,\n        )\n        self.assertEqual(2048, self.cahandler.key_size)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertFalse(self.cahandler.eab_kid)\n        self.assertEqual(\"eab_hmac_key\", self.cahandler.eab_hmac_key)\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_keyfile\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:acme_ca_handler configuration incomplete: \"acme_url\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.acme_keypath)\n        self.assertTrue(self.cahandler.ssl_verify)\n\n    def test_021__challenge_filter(self):\n        \"\"\"test _challenge_filter single http\"\"\"\n        challenge1 = Mock(return_value=\"foo\")\n        challenge1.chall.to_partial_json.return_value = {\"type\": \"http-01\"}\n        challenge1.chall.typ = \"http-01\"\n        challenge1.chall.value = \"value-01\"\n        authz = Mock()\n        authz.body.challenges = [challenge1]\n        self.assertEqual(\"http-01\", self.cahandler._challenge_filter(authz).chall.typ)\n        self.assertEqual(\n            \"value-01\", self.cahandler._challenge_filter(authz).chall.value\n        )\n\n    def test_022__challenge_filter(self):\n        \"\"\"test _challenge_filter dns and http\"\"\"\n        challenge1 = Mock(return_value=\"foo\")\n        challenge1.chall.to_partial_json.return_value = {\"type\": \"dns-01\"}\n        challenge1.chall.typ = \"dns-01\"\n        challenge1.chall.value = \"value-01\"\n        challenge2 = Mock(return_value=\"foo\")\n        challenge2.chall.typ = \"http-01\"\n        challenge2.chall.to_partial_json.return_value = {\"type\": \"http-01\"}\n        challenge2.chall.value = \"value-02\"\n        authz = Mock()\n        authz.body.challenges = [challenge1, challenge2]\n        self.assertEqual(\"http-01\", self.cahandler._challenge_filter(authz).chall.typ)\n        self.assertEqual(\n            \"value-02\", self.cahandler._challenge_filter(authz).chall.value\n        )\n\n    def test_023__challenge_filter(self):\n        \"\"\"test _challenge_filter double http to test break\"\"\"\n        challenge1 = Mock(return_value=\"foo\")\n        challenge1.chall.to_partial_json.return_value = {\"type\": \"http-01\"}\n        challenge1.chall.typ = \"http-01\"\n        challenge1.chall.value = \"value-01\"\n        challenge2 = Mock(return_value=\"foo\")\n        challenge2.chall.to_partial_json.return_value = {\"type\": \"http-01\"}\n        challenge2.chall.typ = \"http-01\"\n        challenge2.chall.value = \"value-02\"\n        authz = Mock()\n        authz.body.challenges = [challenge1, challenge2]\n        self.assertEqual(\"http-01\", self.cahandler._challenge_filter(authz).chall.typ)\n        self.assertEqual(\n            \"value-01\", self.cahandler._challenge_filter(authz).chall.value\n        )\n\n    def test_024__challenge_filter(self):\n        \"\"\"test _challenge_filter no http challenge\"\"\"\n        challenge1 = Mock(return_value=\"foo\")\n        challenge1.chall.to_partial_json.return_value = {\"type\": \"type-01\"}\n        challenge1.chall.typ = \"type-01\"\n        challenge1.chall.value = \"value-01\"\n        challenge2 = Mock(return_value=\"foo\")\n        challenge2.chall.to_partial_json.return_value = {\"type\": \"type-02\"}\n        challenge2.chall.typ = \"type-02\"\n        challenge2.chall.value = \"value-02\"\n        authz = Mock()\n        authz.body.challenges = [challenge1, challenge2]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._challenge_filter(authz))\n        self.assertIn(\n            \"ERROR:test_a2c:Could not find challenge of type http-01\",\n            lcm.output,\n        )\n\n    def test_025__http_challenge_store(self):\n        \"\"\"test _http_challenge_store() no challenge_content\"\"\"\n        # mock_add.return_value = 'ff'\n        self.cahandler._http_challenge_store(\"challenge_name\", None)\n        self.assertFalse(self.cahandler.dbstore.cahandler_add.called)\n\n    def test_026__http_challenge_store(self):\n        \"\"\"test _http_challenge_store() no challenge_content\"\"\"\n        # mock_add.return_value = 'ff'\n        self.cahandler._http_challenge_store(None, \"challenge_content\")\n        self.assertFalse(self.cahandler.dbstore.cahandler_add.called)\n\n    def test_027__http_challenge_store(self):\n        \"\"\"test _http_challenge_store()\"\"\"\n        self.cahandler._http_challenge_store(\"challenge_name\", \"challenge_content\")\n        self.assertTrue(self.cahandler.dbstore.cahandler_add.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    def test_028__challenge_info(self, mock_filter):\n        \"\"\"test _challenge_info - all ok\"\"\"\n        response = Mock()\n        response.chall.validation = Mock(return_value=\"foo.bar\")\n        mock_filter.return_value = response\n        self.assertIn(\"foo\", self.cahandler._challenge_info(\"authzr\", \"user_key\")[0])\n        self.assertIn(\n            \"foo.bar\", self.cahandler._challenge_info(\"authzr\", \"user_key\")[1]\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    def test_029__challenge_info(self, mock_filter):\n        \"\"\"test _challenge_info - wrong split\"\"\"\n        response = Mock()\n        response.chall.validation = Mock(return_value=\"foobar\")\n        mock_filter.return_value = response\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            response = self.cahandler._challenge_info(\"authzr\", \"user_key\")\n        self.assertFalse(response[0])\n        self.assertIn(\"foobar\", response[1])\n        self.assertIn(\n            \"ERROR:test_a2c:Challenge split failed: foobar\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    def test_030__challenge_info(self, mock_filter):\n        \"\"\"test _challenge_info - wrong split\"\"\"\n        response = Mock()\n        response.chall.validation = Mock(return_value=\"foobar\")\n        mock_filter.return_value = response\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None), self.cahandler._challenge_info(None, \"user_key\")\n            )\n        self.assertIn(\"ERROR:test_a2c:acme authorization is missing\", lcm.output)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    def test_031__challenge_info(self, mock_filter):\n        \"\"\"test _challenge_info - wrong split\"\"\"\n        response = Mock()\n        response.chall.validation = Mock(return_value=\"foobar\")\n        mock_filter.return_value = response\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None), self.cahandler._challenge_info(\"authzr\", None)\n            )\n        self.assertIn(\"ERROR:test_a2c:acme user is missing\", lcm.output)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    def test_032__challenge_info(self, mock_filter):\n        \"\"\"test _challenge_info - all ok\"\"\"\n        challenge1 = Mock(return_value=\"foo\")\n        challenge1.to_partial_json.return_value = {\"foo\": \"bar\"}\n        challenge1.chall.typ = \"http-01\"\n        challenge1.chall.value = \"value-01\"\n        mock_filter.side_effect = [None, challenge1]\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._challenge_info(\"authzr\", \"user_key\")[1]\n        )\n\n    @patch(\"josepy.JWKRSA\")\n    def test_033__key_generate(self, mock_key):\n        \"\"\"test _key_generate()\"\"\"\n        mock_key.return_value = \"key\"\n        self.assertEqual(\"key\", self.cahandler._key_generate())\n\n    @patch(\"json.loads\")\n    @patch(\"josepy.JWKRSA.fields_from_json\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_034__user_key_load(self, mock_file, mock_key, mock_json):\n        \"\"\"test user_key_load for an existing file\"\"\"\n        mock_file.return_value = True\n        mock_key.return_value = \"loaded_key\"\n        mock_json.return_value = {\"foo\": \"foo\"}\n        self.assertEqual(\"loaded_key\", self.cahandler._user_key_load())\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_json.called)\n        self.assertFalse(self.cahandler.account)\n\n    @patch(\"json.loads\")\n    @patch(\"josepy.JWKRSA.fields_from_json\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_035__user_key_load(self, mock_file, mock_key, mock_json):\n        \"\"\"test user_key_load for an existing file\"\"\"\n        mock_file.return_value = True\n        mock_key.return_value = \"loaded_key\"\n        mock_json.return_value = {\"account\": \"account\"}\n        self.assertEqual(\"loaded_key\", self.cahandler._user_key_load())\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_json.called)\n        self.assertEqual(\"account\", self.cahandler.account)\n\n    @patch(\"json.dumps\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._key_generate\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_036__user_key_load(self, mock_file, mock_key, mock_json):\n        \"\"\"test user_key_load for an existing file\"\"\"\n        mock_file.return_value = False\n        mock_key.to_json.return_value = {\"foo\": \"generate_key\"}\n        mock_json.return_value = \"foo\"\n        self.assertTrue(self.cahandler._user_key_load())\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_json.called)\n\n    @patch(\"json.dumps\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._key_generate\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_037__user_key_load(self, mock_file, mock_key, mock_json):\n        \"\"\"test user_key_load for an existing file\"\"\"\n        mock_file.return_value = False\n        mock_key.to_json.return_value = {\"foo\": \"generate_key\"}\n        mock_json.side_effect = Exception(\"ex_dump\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertTrue(self.cahandler._user_key_load())\n        self.assertIn(\"ERROR:test_a2c:Error during key dumping: ex_dump\", lcm.output)\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_json.called)\n\n    @patch(\"acme.messages\")\n    def test_038__account_register(self, mock_messages):\n        \"\"\"test account register existing account - no replacement\"\"\"\n        response = Mock()\n        response.uri = \"uri\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"uri\",\n                self.cahandler._account_register(acmeclient, \"user_key\", directory).uri,\n            )\n        self.assertIn(\n            \"INFO:test_a2c:acme-account id is uri. Please add an corresponding acme_account parameter to your acme_srv.cfg to avoid unnecessary lookups\",\n            lcm.output,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_039__account_register(self, mock_messages):\n        \"\"\"test account register existing account - url replacement\"\"\"\n        response = Mock()\n        response.uri = \"urluri\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"urluri\",\n                self.cahandler._account_register(acmeclient, \"user_key\", directory).uri,\n            )\n        self.assertIn(\n            \"INFO:test_a2c:acme-account id is uri. Please add an corresponding acme_account parameter to your acme_srv.cfg to avoid unnecessary lookups\",\n            lcm.output,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_040__account_register(self, mock_messages):\n        \"\"\"test account register existing account - acct_path replacement\"\"\"\n        response = Mock()\n        response.uri = \"acct_pathuri\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"acct_pathuri\",\n                self.cahandler._account_register(acmeclient, \"user_key\", directory).uri,\n            )\n        self.assertIn(\n            \"INFO:test_a2c:acme-account id is uri. Please add an corresponding acme_account parameter to your acme_srv.cfg to avoid unnecessary lookups\",\n            lcm.output,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_041__account_register(self, mock_messages):\n        \"\"\"test account register existing account - with email\"\"\"\n        response = Mock()\n        response.uri = \"newuri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"newuri\",\n                self.cahandler._account_register(\n                    acmeclient, \"user_key\", \"directory\"\n                ).uri,\n            )\n        self.assertIn(\n            \"INFO:test_a2c:acme-account id is newuri. Please add an corresponding acme_account parameter to your acme_srv.cfg to avoid unnecessary lookups\",\n            lcm.output,\n        )\n        self.assertEqual(\"newuri\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_042__account_register(self, mock_messages):\n        \"\"\"test account register existing account - no email\"\"\"\n        response = Mock()\n        response.uri = \"newuri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.cahandler._account_register(acmeclient, \"user_key\", \"directory\")\n            )\n        self.assertFalse(self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_043__account_register(self, mock_messages):\n        \"\"\"test account register existing account - no url\"\"\"\n        response = Mock()\n        response.uri = \"newuri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        self.assertEqual(\n            \"newuri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertFalse(self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_044__account_register(self, mock_messages):\n        \"\"\"test account register existing account - wrong pathdic\"\"\"\n        response = Mock()\n        response.uri = \"newuri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path1\": \"acct_path\"}\n        self.cahandler.acme_url = \"url\"\n        self.assertEqual(\n            \"newuri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertFalse(self.cahandler.account)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._zerossl_eab_get\")\n    @patch(\"acme.messages\")\n    def test_045__account_register(self, mock_messages, mock_eab):\n        \"\"\"test account register existing account - normal url\"\"\"\n        response = Mock()\n        response.uri = \"urluri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        self.cahandler.acme_url = \"url\"\n        self.assertEqual(\n            \"urluri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._zerossl_eab_get\")\n    @patch(\"acme.messages\")\n    def test_046__account_register(self, mock_messages, mock_eab):\n        \"\"\"test account register existing account - zerossl.com url\"\"\"\n        response = Mock()\n        response.uri = \"zerossl.comuri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        self.cahandler.acme_url = \"zerossl.com\"\n        self.cahandler.acme_url_dic = {\"host\": \"acme.zerossl.com\"}\n        self.assertEqual(\n            \"zerossl.comuri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.messages.ExternalAccountBinding.from_data\"\n    )\n    @patch(\"acme.messages\")\n    def test_047__account_register(self, mock_messages, mock_eab):\n        \"\"\"test account register existing account - zerossl.com url\"\"\"\n        response = Mock()\n        response.uri = \"urluri\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_messages = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        self.cahandler.acme_url = \"url\"\n        self.assertEqual(\n            \"urluri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._jwk_strip\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.messages.ExternalAccountBinding.from_data\"\n    )\n    @patch(\"acme.messages\")\n    def test_048__account_register(self, mock_messages, mock_eab, mock_jwk_strip):\n        \"\"\"test account register existing account - zerossl.com url\"\"\"\n        response = Mock()\n        response.uri = \"urluri\"\n        mock_jwk_strip.return_value = \"user_key\"\n        acmeclient = Mock()\n        acmeclient.new_account = Mock(return_value=response)\n        mock_eab.return_value = Mock()\n        self.cahandler.email = \"email\"\n        self.cahandler.path_dic = {\"acct_path\": \"acct_path\"}\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.eab_kid = \"kid\"\n        self.cahandler.eab_hmac_key = \"hmac_key\"\n        self.assertEqual(\n            \"urluri\",\n            self.cahandler._account_register(acmeclient, \"user_key\", \"directory\").uri,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"acme.messages.NewRegistration.from_data\")\n    def test_049_acount_create(self, mock_newreg):\n        \"\"\"test account_create\"\"\"\n        response = \"response\"\n        acmeclient = Mock()\n        acmeclient.new_account.return_value = \"response\"\n        self.cahandler.email = \"email\"\n        self.assertEqual(\n            \"response\",\n            self.cahandler._account_create(acmeclient, \"user_key\", \"directory\"),\n        )\n        self.assertTrue(mock_newreg.called)\n\n    @patch(\"acme.messages.NewRegistration.from_data\")\n    def test_050_acount_create(self, mock_newreg):\n        \"\"\"test account_create\"\"\"\n        response = \"response\"\n        acmeclient = Mock()\n        acmeclient.new_account.side_effect = Exception(\"mock_exception\")\n        self.cahandler.email = \"email\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.cahandler._account_create(acmeclient, \"user_key\", \"directory\")\n            )\n        self.assertTrue(mock_newreg.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Account registration failed: mock_exception\",\n            lcm.output,\n        )\n\n    @patch(\"acme.messages.NewRegistration.from_data\")\n    def test_051_acount_create(self, mock_newreg):\n        \"\"\"test account_create\"\"\"\n        response = \"response\"\n        acmeclient = Mock()\n        acmeclient.new_account.side_effect = Exception(\"ConflictError\")\n        self.cahandler.email = \"email\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.cahandler._account_create(acmeclient, \"user_key\", \"directory\")\n            )\n        self.assertTrue(mock_newreg.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Account registration failed: ConflictError\",\n            lcm.output,\n        )\n\n    def test_052_trigger(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Not implemented\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_053_poll(self):\n        \"\"\"test poll\"\"\"\n        self.assertEqual(\n            (\"Not implemented\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._registration_lookup\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_054_enroll(\n        self, mock_messages, mock_clientnw, mock_key, mock_reg, mock_enroll, mock_ecl\n    ):\n        \"\"\"test enroll registration error\"\"\"\n        mock_key.return_value = \"key\"\n        mock_reg.return_value = \"mock_reg\"\n        mock_enroll.return_value = (\"error\", \"fullchain\", \"raw\")\n        self.assertEqual(\n            (\"error\", \"fullchain\", \"raw\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._registration_lookup\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_055_enroll(\n        self,\n        mock_messages,\n        mock_clientnw,\n        mock_key,\n        mock_reg,\n        mock_enroll,\n        mock_ecl,\n        mock_adl,\n    ):\n        \"\"\"test enroll registration error\"\"\"\n        mock_key.return_value = \"key\"\n        mock_adl.return_value = \"mock_adl\"\n        mock_reg.return_value = \"mock_reg\"\n        mock_enroll.return_value = (\"error\", \"fullchain\", \"raw\")\n        self.assertEqual((\"mock_adl\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertFalse(mock_ecl.called)\n        self.assertFalse(mock_ecl.called)\n        self.assertFalse(mock_key.called)\n        self.assertFalse(mock_reg.called)\n        self.assertFalse(mock_clientnw.called)\n        self.assertFalse(mock_messages.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._registration_lookup\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_056_enroll(\n        self, mock_messages, mock_clientnw, mock_key, mock_reg, mock_enroll, mock_ecl\n    ):\n        \"\"\"test enroll registration error\"\"\"\n        mock_key.return_value = \"key\"\n        mock_reg.return_value = \"mock_reg\"\n        self.cahandler.enrollment_config_log = True\n        mock_enroll.return_value = (\"error\", \"fullchain\", \"raw\")\n        self.assertEqual(\n            (\"error\", \"fullchain\", \"raw\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._registration_lookup\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_057_enroll(\n        self, mock_messages, mock_clientnw, mock_key, mock_reg, mock_enroll\n    ):\n        \"\"\"test enroll registration error\"\"\"\n        mock_key.return_value = \"key\"\n        mock_reg.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Account registration failed\", None, None, None),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertFalse(mock_enroll.called)\n        self.assertIn(\"ERROR:test_a2c:Account registration failed\", lcm.output)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.poll_and_finalize\")\n    @patch(\"acme.client.ClientV2.answer_challenge\")\n    @patch(\"acme.client.ClientV2.new_order\")\n    @patch(\"acme.client.ClientNetwork\")\n    def test_058_enroll(\n        self,\n        mock_clientnw,\n        mock_c2o,\n        mock_ach,\n        mock_pof,\n        mock_key,\n        mock_reg,\n        mock_cinfo,\n        mock_store,\n        mock_pem2der,\n        mock_encode,\n    ):\n        \"\"\"test enroll with no account configured\"\"\"\n        mock_key.return_value = \"key\"\n        response = Mock()\n        response.body.status = \"valid\"\n        mock_reg.return_value = response\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        from acme import messages\n\n        authzr.body.status = messages.STATUS_PENDING\n        challenge = Mock()\n        challenge.chall = Mock()\n        challenge.chall.response = Mock(return_value=\"response\")\n        challenge.response_and_validation.return_value = (Mock(), \"validation\")\n        challenge.response.return_value = \"response\"\n        challenge.status = \"valid\"\n        authzr.body.challenges = [challenge]\n        order.authorizations = [authzr]\n        acmeclient = Mock()\n        acmeclient.answer_challenge.return_value = Mock()\n        user_key = Mock()\n        mock_cinfo.return_value = (\"http-01\", \"content\", challenge)\n        result = self.cahandler._order_authorization(acmeclient, order, user_key)\n        self.assertTrue(result)\n\n        # Ensure enroll uses the correct mock authorization\n        mock_c2o.return_value.authorizations = [authzr]\n\n        chall = Mock()\n        chall.chall = Mock()\n        chall.chall.response = Mock(return_value=\"response\")\n        chall.response_and_validation.return_value = (Mock(), \"validation\")\n        chall.response.return_value = \"response\"\n        chall.status = \"valid\"\n        mock_ach.return_value = \"auth_response\"\n        mock_cinfo.return_value = (\"challenge_name\", \"challenge_content\", chall)\n        resp_pof = Mock()\n        resp_pof.fullchain_pem = \"fullchain\"\n        mock_pof.return_value = resp_pof\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_encode.return_value = \"mock_encode\"\n        self.assertEqual(\n            (None, \"fullchain\", \"mock_encode\", None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_ach.called)\n        self.assertTrue(mock_reg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.poll_and_finalize\")\n    @patch(\"acme.client.ClientV2.answer_challenge\")\n    @patch(\"acme.client.ClientV2.new_order\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_059_enroll(\n        self,\n        mock_messages,\n        mock_clientnw,\n        mock_c2o,\n        mock_ach,\n        mock_pof,\n        mock_key,\n        mock_reg,\n        mock_cinfo,\n        mock_store,\n        mock_pem2der,\n        mock_encode,\n        mock_csrchk,\n    ):\n        \"\"\"test enroll with existing account\"\"\"\n        self.cahandler.account = \"account\"\n        mock_key.return_value = \"key\"\n        mock_messages = Mock()\n        response = Mock()\n        response.body.status = \"valid\"\n        mock_reg.return_value = response\n        mock_norder = Mock()\n        challenge = Mock()\n        challenge.response_and_validation.return_value = (Mock(), \"validation\")\n        challenge.response.return_value = \"response\"\n        authzr1 = Mock()\n        authzr1.body = Mock()\n        authzr1.body.status = \"valid\"\n        authzr1.body.challenges = [challenge]\n        authzr2 = Mock()\n        authzr2.body = Mock()\n        authzr2.body.status = \"valid\"\n        authzr2.body.challenges = [challenge]\n        mock_norder.authorizations = [authzr1, authzr2]\n\n        def order_auth_side_effect(acmeclient_arg, order, user_key):\n            mock_store()\n            mock_ach()\n            return True\n\n        self.cahandler._order_authorization = Mock(side_effect=order_auth_side_effect)\n        mock_c2o.return_value = mock_norder\n        chall = Mock()\n        mock_ach.return_value = \"auth_response\"\n        mock_cinfo.return_value = (\"challenge_name\", \"challenge_content\", chall)\n        resp_pof = Mock()\n        resp_pof.fullchain_pem = \"fullchain\"\n        mock_pof.return_value = resp_pof\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_encode.return_value = \"mock_encode\"\n        mock_csrchk.return_value = False\n        self.assertEqual(\n            (None, \"fullchain\", \"mock_encode\", None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_ach.called)\n        self.assertTrue(mock_reg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.poll_and_finalize\")\n    @patch(\"acme.client.ClientV2.answer_challenge\")\n    @patch(\"acme.client.ClientV2.new_order\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_060_enroll(\n        self,\n        mock_messages,\n        mock_clientnw,\n        mock_c2o,\n        mock_ach,\n        mock_pof,\n        mock_key,\n        mock_reg,\n        mock_cinfo,\n        mock_store,\n        mock_dumpcert,\n        mock_loadcert,\n        mock_csrchk,\n    ):\n        \"\"\"test enroll with bodystatus invalid\"\"\"\n        mock_key.return_value = \"key\"\n        mock_messages = Mock()\n        response = Mock()\n        response.body.status = \"invalid\"\n        response.body.error = \"error\"\n        mock_reg.return_value = response\n        mock_norder = Mock()\n        mock_norder.authorizations = [\"1\", \"2\"]\n        mock_c2o.return_value = mock_norder\n        chall = Mock()\n        mock_ach.return_value = \"auth_response\"\n        mock_cinfo.return_value = (\"challenge_name\", \"challenge_content\", chall)\n        resp_pof = Mock()\n        resp_pof.fullchain_pem = \"fullchain\"\n        mock_pof.return_value = resp_pof\n        mock_dumpcert.return_value = b\"mock_dumpcert\"\n        mock_loadcert.return_value = \"mock_loadcert\"\n        mock_csrchk.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Bad ACME account: error\", None, None, None),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_ach.called)\n        self.assertTrue(mock_reg.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment failed: Bad ACME account: error\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.poll_and_finalize\")\n    @patch(\"acme.client.ClientV2.answer_challenge\")\n    @patch(\"acme.client.ClientV2.new_order\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_061_enroll(\n        self,\n        mock_messages,\n        mock_clientnw,\n        mock_c2o,\n        mock_ach,\n        mock_pof,\n        mock_key,\n        mock_reg,\n        mock_cinfo,\n        mock_store,\n        mock_dumpcert,\n        mock_loadcert,\n        mock_csrchk,\n    ):\n        \"\"\"test enroll with no fullchain\"\"\"\n        mock_key.return_value = \"key\"\n        mock_messages = Mock()\n        response = Mock()\n        response.body.status = \"valid\"\n        mock_reg.return_value = response\n        mock_norder = Mock()\n        challenge = Mock()\n        challenge.response_and_validation.return_value = (Mock(), \"validation\")\n        challenge.response.return_value = \"response\"\n        authzr1 = Mock()\n        authzr1.body = Mock()\n        authzr1.body.status = \"valid\"\n        authzr1.body.challenges = [challenge]\n        authzr2 = Mock()\n        authzr2.body = Mock()\n        authzr2.body.status = \"valid\"\n        authzr2.body.challenges = [challenge]\n        mock_norder.authorizations = [authzr1, authzr2]\n        acmeclient = Mock()\n        acmeclient.answer_challenge.return_value = Mock()\n        patcher = patch(\"acme.client.ClientV2.answer_challenge\", return_value=Mock())\n        patcher.start()\n        self.addCleanup(patcher.stop)\n        mock_c2o.return_value = mock_norder\n        chall = Mock()\n        mock_ach.return_value = \"auth_response\"\n        mock_cinfo.return_value = (\"challenge_name\", \"challenge_content\", chall)\n        resp_pof = Mock()\n        resp_pof.fullchain_pem = None\n        resp_pof.error = \"order_error\"\n        mock_pof.return_value = resp_pof\n        mock_dumpcert.return_value = b\"mock_dumpcert\"\n        mock_loadcert.return_value = \"mock_loadcert\"\n        mock_csrchk.return_value = False\n\n        def order_auth_side_effect(acmeclient_arg, order, user_key):\n            mock_store()\n            mock_ach()\n            return True\n\n        self.cahandler._order_authorization = Mock(side_effect=order_auth_side_effect)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Error getting certificate: order_error\", None, None, None),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Error getting certificate: order_error\",\n            lcm.output,\n        )\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_ach.called)\n        self.assertTrue(mock_reg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    def test_062_enroll(\n        self, mock_key, mock_store, mock_reg, mock_nw, mock_newreg, mock_csrchk\n    ):\n        \"\"\"test enroll exception during enrollment\"\"\"\n        mock_csrchk.return_value = False\n        mock_key.side_effect = Exception(\"ex_user_key_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"ex_user_key_load\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment error: ex_user_key_load\", lcm.output)\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_nw.called)\n        self.assertFalse(mock_reg.called)\n        self.assertFalse(mock_newreg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    def test_063_enroll(\n        self,\n        mock_key,\n        mock_store,\n        mock_reg,\n        mock_nw,\n        mock_newreg,\n        mock_csrchk,\n        mock_profilechk,\n    ):\n        \"\"\"test enroll exception during enrollment\"\"\"\n        mock_profilechk.return_value = False\n        mock_csrchk.return_value = \"error\"\n        self.cahandler.allowed_domainlist = [\"allowed_domain\"]\n        mock_key.side_effect = Exception(\"ex_user_key_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error: CSR rejected. error\", lcm.output\n        )\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_nw.called)\n        self.assertFalse(mock_reg.called)\n        self.assertFalse(mock_newreg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    def test_064_enroll(\n        self,\n        mock_key,\n        mock_store,\n        mock_reg,\n        mock_nw,\n        mock_newreg,\n        mock_csrchk,\n        mock_profilechk,\n    ):\n        \"\"\"test enroll exception during enrollment\"\"\"\n        mock_profilechk.return_value = False\n        mock_csrchk.return_value = \"error\"\n        self.cahandler.allowed_domainlist = [\"allowed_domain\"]\n        mock_key.side_effect = Exception(\"ex_user_key_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error: CSR rejected. error\", lcm.output\n        )\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_nw.called)\n        self.assertFalse(mock_reg.called)\n        self.assertFalse(mock_newreg.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._order_issue\")\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._http_challenge_store\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_register\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.poll_and_finalize\")\n    @patch(\"acme.client.ClientV2.answer_challenge\")\n    @patch(\"acme.client.ClientV2.new_order\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"acme.messages\")\n    def test_065_enroll(\n        self,\n        mock_messages,\n        mock_clientnw,\n        mock_c2o,\n        mock_ach,\n        mock_pof,\n        mock_key,\n        mock_reg,\n        mock_cinfo,\n        mock_store,\n        mock_dumpcert,\n        mock_loadcert,\n        mock_csrchk,\n        mock_issue,\n    ):\n        \"\"\"test enroll with bodystatus None (existing account)\"\"\"\n        mock_key.return_value = \"key\"\n        mock_messages = Mock()\n        response = Mock()\n        response.body.status = None\n        response.uri = \"uri\"\n        mock_reg.return_value = response\n        mock_norder = Mock()\n        mock_norder.authorizations = [\"1\", \"2\"]\n        mock_c2o.return_value = mock_norder\n        chall = Mock()\n        mock_ach.return_value = \"auth_response\"\n        mock_cinfo.return_value = (\"challenge_name\", \"challenge_content\", chall)\n        resp_pof = Mock()\n        resp_pof.fullchain_pem = \"fullchain\"\n        mock_pof.return_value = resp_pof\n        mock_dumpcert.return_value = b\"mock_dumpcert\"\n        mock_loadcert.return_value = \"mock_loadcert\"\n        mock_csrchk.return_value = False\n        mock_issue.return_value = (\"error\", \"cert\", \"raw\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"error\", \"cert\", \"raw\", None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_ach.called)\n        self.assertTrue(mock_reg.called)\n        self.assertTrue(mock_issue.called)\n        self.assertIn(\n            \"INFO:test_a2c:Existing but not configured ACME account: uri\", lcm.output\n        )\n\n    @patch(\"acme.messages\")\n    def test_066__account_lookup(self, mock_messages):\n        \"\"\"test account register existing account - no replacement\"\"\"\n        response = Mock()\n        response.uri = \"urluriacc_info\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._account_lookup(acmeclient, \"reg\", directory)\n        self.assertIn(\n            \"INFO:test_a2c:Found existing account: urluriacc_info\",\n            lcm.output,\n        )\n        self.assertEqual(\"urluriacc_info\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_067__account_lookup(self, mock_messages):\n        \"\"\"test account register existing account - url replacement\"\"\"\n        response = Mock()\n        response.uri = \"urluriacc_info\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.acme_url = \"url\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._account_lookup(acmeclient, \"reg\", directory)\n        self.assertIn(\n            \"INFO:test_a2c:Found existing account: urluriacc_info\",\n            lcm.output,\n        )\n        self.assertEqual(\"uriacc_info\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_068__account_lookup(self, mock_messages):\n        \"\"\"test account register existing account - acct_path replacement\"\"\"\n        response = Mock()\n        response.uri = \"urluriacc_info\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.path_dic = {\"acct_path\": \"acc_info\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._account_lookup(acmeclient, \"reg\", directory)\n        self.assertIn(\n            \"INFO:test_a2c:Found existing account: urluriacc_info\",\n            lcm.output,\n        )\n        self.assertEqual(\"urluri\", self.cahandler.account)\n\n    @patch(\"acme.messages\")\n    def test_069__account_lookup(self, mock_messages):\n        \"\"\"test account register existing account - acct_path replacement\"\"\"\n        response = Mock()\n        response.uri = \"urluriacc_info\"\n        acmeclient = Mock()\n        acmeclient.query_registration = Mock(return_value=response)\n        mock_messages = Mock()\n        directory = {\"newAccount\": \"newAccount\"}\n        self.cahandler.acme_url = \"url\"\n        self.cahandler.path_dic = {\"acct_path\": \"acc_info\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._account_lookup(acmeclient, \"reg\", directory)\n        self.assertIn(\n            \"INFO:test_a2c:Found existing account: urluriacc_info\",\n            lcm.output,\n        )\n        self.assertEqual(\"uri\", self.cahandler.account)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.revoke\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.messages\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    @patch(\"os.path.exists\")\n    def test_070_revoke(\n        self,\n        mock_exists,\n        mock_load,\n        mock_nw,\n        mock_mess,\n        mock_reg,\n        mock_revoke,\n        mock_key,\n        mock_eabrevchk,\n    ):\n        \"\"\"test revoke successful\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        self.cahandler.account = \"account\"\n        mock_exists.return_value = True\n        mock_load.return_value = \"mock_load_cert\"\n        response = Mock()\n        response.body.status = \"valid\"\n        mock_reg.return_value = response\n        self.assertEqual(\n            (200, None, None), self.cahandler.revoke(\"cert\", \"reason\", \"date\")\n        )\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_nw.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertFalse(mock_eabrevchk.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.revoke\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.messages\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    @patch(\"os.path.exists\")\n    def test_071_revoke(\n        self,\n        mock_exists,\n        mock_load,\n        mock_nw,\n        mock_mess,\n        mock_reg,\n        mock_revoke,\n        mock_key,\n        mock_eabrevchk,\n    ):\n        \"\"\"test revoke successful\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        self.cahandler.account = \"account\"\n        mock_exists.return_value = True\n        mock_load.return_value = \"mock_load_cert\"\n        response = Mock()\n        response.body.status = \"valid\"\n        mock_reg.return_value = response\n        self.cahandler.eab_profiling = True\n        self.assertEqual(\n            (200, None, None), self.cahandler.revoke(\"cert\", \"reason\", \"date\")\n        )\n        self.assertTrue(mock_key.called)\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_nw.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertTrue(mock_eabrevchk.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"acme.client.ClientV2.revoke\")\n    @patch(\"acme.client.ClientV2.query_registration\")\n    @patch(\"acme.messages\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    @patch(\"os.path.exists\")\n    def test_072_revoke(\n        self,\n        mock_exists,\n        mock_load,\n        mock_nw,\n        mock_mess,\n        mock_reg,\n        mock_revoke,\n        mock_key,\n    ):\n        \"\"\"test revoke invalid status after reglookup\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        self.cahandler.account = \"account\"\n        mock_exists.return_value = True\n        mock_load.return_value = \"mock_load_cert\"\n        response = Mock()\n        response.body.status = \"invalid\"\n        response.body.error = \"error\"\n        mock_reg.return_value = response\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Bad ACME account: error\",\n            ),\n            self.cahandler.revoke(\"cert\", \"reason\", \"date\"),\n        )\n        self.assertTrue(mock_key.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_nw.called)\n        self.assertFalse(mock_revoke.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_lookup\")\n    @patch(\"acme.messages\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    @patch(\"os.path.exists\")\n    def test_073_revoke(\n        self,\n        mock_exists,\n        mock_load,\n        mock_nw,\n        mock_mess,\n        mock_lookup,\n        mock_key,\n    ):\n        \"\"\"test revoke account lookup failed\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        mock_exists.return_value = True\n        mock_load.return_value = \"mock_load_cert\"\n        self.assertEqual(\n            (500, \"urn:ietf:params:acme:error:serverInternal\", \"account lookup failed\"),\n            self.cahandler.revoke(\"cert\", \"reason\", \"date\"),\n        )\n        self.assertTrue(mock_lookup.called)\n        self.assertTrue(mock_key.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_nw.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._account_lookup\")\n    @patch(\"acme.messages\")\n    @patch(\"acme.client.ClientNetwork\")\n    @patch(\"josepy.JWKRSA\")\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    @patch(\"os.path.exists\")\n    def test_074_revoke(\n        self,\n        mock_exists,\n        mock_load,\n        mock_kload,\n        mock_nw,\n        mock_mess,\n        mock_lookup,\n    ):\n        \"\"\"test revoke user key load failed\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        mock_exists.return_value = False\n        mock_load.return_value = \"mock_load_cert\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"urn:ietf:params:acme:error:serverInternal\", \"Internal Error\"),\n                self.cahandler.revoke(\"cert\", \"reason\", \"date\"),\n            )\n        self.assertFalse(mock_lookup.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Error during revocation: Could not load user_key keyfile\",\n            lcm.output,\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"mock_open\"), create=True)\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._user_key_load\")\n    @patch(\"os.path.exists\")\n    def test_075_revoke(self, mock_exists, mock_load):\n        \"\"\"test revoke exception during processing\"\"\"\n        self.cahandler.acme_keyfile = \"keyfile\"\n        mock_exists.return_value = True\n        mock_load.side_effect = Exception(\"ex_user_key_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"urn:ietf:params:acme:error:serverInternal\", \"ex_user_key_load\"),\n                self.cahandler.revoke(\"cert\", \"reason\", \"date\"),\n            )\n        self.assertIn(\"ERROR:test_a2c:Revocation error: ex_user_key_load\", lcm.output)\n\n    @patch(\"requests.post\")\n    def test_076__zerossl_eab_get(self, mock_post):\n        \"\"\"CAhandler._zerossl_eab_get() - all ok\"\"\"\n        mock_post.return_value.json.return_value = {\n            \"success\": True,\n            \"eab_kid\": \"eab_kid\",\n            \"eab_hmac_key\": \"eab_hmac_key\",\n        }\n        self.cahandler._zerossl_eab_get()\n        self.assertTrue(mock_post.called)\n        self.assertEqual(\"eab_kid\", self.cahandler.eab_kid)\n        self.assertEqual(\"eab_hmac_key\", self.cahandler.eab_hmac_key)\n\n    @patch(\"requests.post\")\n    def test_077__zerossl_eab_get(self, mock_post):\n        \"\"\"CAhandler._zerossl_eab_get() - success false\"\"\"\n        mock_post.return_value.json.return_value = {\n            \"success\": False,\n            \"eab_kid\": \"eab_kid\",\n            \"eab_hmac_key\": \"eab_hmac_key\",\n        }\n        mock_post.return_value.text = \"text\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._zerossl_eab_get()\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.eab_kid)\n        self.assertFalse(self.cahandler.eab_hmac_key)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get eab credentials from ZeroSSL: text\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_078__zerossl_eab_get(self, mock_post):\n        \"\"\"CAhandler._zerossl_eab_get() - no success key\"\"\"\n        mock_post.return_value.json.return_value = {\n            \"eab_kid\": \"eab_kid\",\n            \"eab_hmac_key\": \"eab_hmac_key\",\n        }\n        mock_post.return_value.text = \"text\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._zerossl_eab_get()\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.eab_kid)\n        self.assertFalse(self.cahandler.eab_hmac_key)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get eab credentials from ZeroSSL: text\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_079__zerossl_eab_get(self, mock_post):\n        \"\"\"CAhandler._zerossl_eab_get() - no eab_kid key\"\"\"\n        mock_post.return_value.json.return_value = {\n            \"success\": True,\n            \"eab_hmac_key\": \"eab_hmac_key\",\n        }\n        mock_post.return_value.text = \"text\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._zerossl_eab_get()\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.eab_kid)\n        self.assertFalse(self.cahandler.eab_hmac_key)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get eab credentials from ZeroSSL: text\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_080__zerossl_eab_get(self, mock_post):\n        \"\"\"CAhandler._zerossl_eab_get() - no eab_mac key\"\"\"\n        mock_post.return_value.json.return_value = {\n            \"success\": True,\n            \"eab_kid\": \"eab_kid\",\n        }\n        mock_post.return_value.text = \"text\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._zerossl_eab_get()\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.eab_kid)\n        self.assertFalse(self.cahandler.eab_hmac_key)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get eab credentials from ZeroSSL: text\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    def test_081__order_authorization(self, mock_info):\n        \"\"\"CAhandler._order_authorization - sectigo challenge\"\"\"\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        from acme import messages\n\n        authzr.body.status = messages.STATUS_PENDING\n        challenge = Mock()\n        challenge.chall = Mock()\n        challenge.chall.response = Mock(return_value=\"response\")\n        challenge.response_and_validation.return_value = (Mock(), \"validation\")\n        challenge.response.return_value = \"response\"\n        challenge.status = \"valid\"\n        authzr.body.challenges = [challenge]\n        order.authorizations = [authzr]\n        mock_info.return_value = [\n            None,\n            {\"type\": \"sectigo-email-01\", \"status\": \"valid\"},\n            challenge,\n        ]\n        acmeclient = Mock()\n        acmeclient.answer_challenge.return_value = Mock()\n        self.assertTrue(\n            self.cahandler._order_authorization(acmeclient, order, \"user_key\")\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    def test_082__order_authorization(self, mock_info):\n        \"\"\"CAhandler._order_authorization - sectigo challenge\"\"\"\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        authzr.body.status = \"invalid\"\n        order.authorizations = [authzr]\n        mock_info.return_value = [\n            None,\n            {\"type\": \"sectigo-email-01\", \"status\": \"invalid\"},\n            \"challenge\",\n        ]\n        self.assertFalse(\n            self.cahandler._order_authorization(\"acmeclient\", order, \"user_key\")\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    def test_083__order_authorization(self, mock_info):\n        \"\"\"CAhandler._order_authorization - sectigo challenge\"\"\"\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        from acme import messages\n\n        authzr.body.status = messages.STATUS_VALID\n        order.authorizations = [authzr]\n        mock_info.return_value = [\n            None,\n            {\"type\": \"unk-01\", \"status\": \"valid\"},\n            \"challenge\",\n        ]\n        self.assertTrue(\n            self.cahandler._order_authorization(\"acmeclient\", order, \"user_key\")\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    def test_084__order_authorization(self, mock_info):\n        \"\"\"CAhandler._order_authorization - sectigo challenge\"\"\"\n        order = Mock()\n        authzr = Mock()\n        authzr.body = Mock()\n        authzr.body.status = \"valid\"\n        order.authorizations = [authzr]\n        mock_info.return_value = [None, \"string\", \"challenge\"]\n        self.assertFalse(\n            self.cahandler._order_authorization(\"acmeclient\", order, \"user_key\")\n        )\n\n    def test_085_eab_profile_list_check(self):\n        \"\"\"test eab_profile_list_check\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.cahandler.eab_profile_list_check(\n                    \"eab_handler\", \"csr\", \"acme_keyfile\", \"key_file\"\n                )\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:acme_keyfile is not allowed in profile\",\n            lcm.output,\n        )\n\n    def test_086_eab_profile_list_check(self):\n        \"\"\"test eab_profile_list_check\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"acme_keypath is missing in config\",\n                self.cahandler.eab_profile_list_check(\n                    \"eab_handler\", \"csr\", \"acme_url\", \"acme_url\"\n                ),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:acme_keypath is missing in config\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_087_eab_profile_list_check(self, mock_hiv):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        self.assertFalse(\n            self.cahandler.eab_profile_list_check(\n                \"eab_handler\", \"csr\", \"acme_url\", \"http://acme_url\"\n            )\n        )\n        self.assertEqual(\"acme_keypath/acme_url.json\", self.cahandler.acme_keyfile)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_088_eab_profile_list_check(self, mock_hiv):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (None, \"error\")\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        self.assertEqual(\n            \"error\",\n            self.cahandler.eab_profile_list_check(\n                \"eab_handler\", \"csr\", \"acme_url\", \"http://acme_url\"\n            ),\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_089_eab_profile_list_check(self, mock_hiv):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        self.assertFalse(\n            self.cahandler.eab_profile_list_check(\n                \"eab_handler\", \"csr\", \"unknown\", \"unknown\"\n            )\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_090_eab_profile_list_check(self, mock_hiv, mock_chk):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        eab_handler = MagicMock()\n        eab_handler.allowed_domains_check.return_value = False\n        self.assertFalse(\n            self.cahandler.eab_profile_list_check(\n                eab_handler, \"csr\", \"allowed_domainlist\", [\"unknown\"]\n            )\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n        self.assertTrue(eab_handler.allowed_domains_check.called)\n        self.assertFalse(mock_chk.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_091_eab_profile_list_check(self, mock_hiv, mock_chk):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        eab_handler = MagicMock()\n        eab_handler.allowed_domains_check.return_value = \"error\"\n        self.assertEqual(\n            \"error\",\n            self.cahandler.eab_profile_list_check(\n                eab_handler, \"csr\", \"allowed_domainlist\", [\"unknown\"]\n            ),\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n        self.assertTrue(eab_handler.allowed_domains_check.called)\n        self.assertFalse(mock_chk.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_092_eab_profile_list_check(self, mock_hiv, mock_chk):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        eab_handler = MagicMock()\n        eab_handler.foo.return_value = \"error\"\n        mock_chk.return_value = \"check_error\"\n        self.assertEqual(\n            \"check_error\",\n            self.cahandler.eab_profile_list_check(\n                eab_handler, \"csr\", \"allowed_domainlist\", [\"unknown\"]\n            ),\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(eab_handler.allowed_domains_check.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client_parameter_validate\")\n    def test_093_eab_profile_list_check(self, mock_hiv, mock_chk):\n        \"\"\"test eab_profile_list_check\"\"\"\n        mock_hiv.return_value = (\"http://acme_url\", None)\n        self.cahandler.acme_keypath = \"acme_keypath\"\n        self.cahandler.acme_keyfile = \"acme_keyfile\"\n        eab_handler = MagicMock()\n        eab_handler.foo.return_value = \"error\"\n        mock_chk.return_value = None\n        self.assertFalse(\n            self.cahandler.eab_profile_list_check(\n                eab_handler, \"csr\", \"allowed_domainlist\", [\"unknown\"]\n            )\n        )\n        self.assertEqual(\"acme_keyfile\", self.cahandler.acme_keyfile)\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(eab_handler.allowed_domains_check.called)\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=\"{}\")\n    def test_094_account_to_keyfile(self, mock_file):\n        \"\"\"test account_to_keyfile\"\"\"\n        self.cahandler.acme_keyfile = \"dummy_keyfile_path\"\n        self.cahandler.account = \"dummy_account\"\n        self.cahandler._account_to_keyfile()\n        self.assertTrue(mock_file.called)\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=\"{}\")\n    def test_095_account_to_keyfile(self, mock_file):\n        \"\"\"test account_to_keyfile\"\"\"\n        self.cahandler.acme_keyfile = \"dummy_keyfile_path\"\n        self.cahandler.account = None\n        self.cahandler._account_to_keyfile()\n        self.assertFalse(mock_file.called)\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=\"{}\")\n    def test_096_account_to_keyfile(self, mock_file):\n        \"\"\"test account_to_keyfile\"\"\"\n        self.cahandler.acme_keyfile = None\n        self.cahandler.account = \"dummy_account\"\n        self.cahandler._account_to_keyfile()\n        self.assertFalse(mock_file.called)\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=\"{}\")\n    def test_097_account_to_keyfile(self, mock_file):\n        \"\"\"test account_to_keyfile\"\"\"\n        self.cahandler.acme_keyfile = \"dummy_keyfile_path\"\n        self.cahandler.account = \"dummy_account\"\n        mock_file.side_effect = Exception(\"ex_json_dump\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._account_to_keyfile()\n        self.assertTrue(mock_file.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not map account to keyfile: ex_json_dump\",\n            lcm.output,\n        )\n\n    def test_098_accountname_get(self):\n        \"\"\"test accountname_get\"\"\"\n        url = \"url\"\n        acme_url = \"acme_url\"\n        path_dic = {\"acct_path\": \"acct_path\"}\n        self.assertEqual(\n            \"url\", self.cahandler._accountname_get(url, acme_url, path_dic)\n        )\n\n    def test_099_accountname_get(self):\n        \"\"\"test accountname_get\"\"\"\n        url = \"acme_url/foo\"\n        acme_url = \"acme_url\"\n        path_dic = {\"acct_path\": \"acct_path\"}\n        self.assertEqual(\n            \"/foo\", self.cahandler._accountname_get(url, acme_url, path_dic)\n        )\n\n    def test_100_accountname_get(self):\n        \"\"\"test accountname_get\"\"\"\n        url = \"acme_url/foo/acct_path\"\n        acme_url = \"acme_url\"\n        path_dic = {\"acct_path\": \"acct_path\"}\n        self.assertEqual(\n            \"/foo/\", self.cahandler._accountname_get(url, acme_url, path_dic)\n        )\n\n    def test_101_accountname_get(self):\n        \"\"\"test accountname_get\"\"\"\n        url = \"acme_url/acct_path/foo\"\n        acme_url = \"acme_url\"\n        path_dic = {\"acct_path\": \"/\"}\n        self.assertEqual(\n            \"acct_path/foo\", self.cahandler._accountname_get(url, acme_url, path_dic)\n        )\n\n    def test_102_accountname_get(self):\n        \"\"\"test accountname_get\"\"\"\n        url = \"acme_url/foo/foo\"\n        acme_url = \"acme_url\"\n        path_dic = {\"foo\": \"bar\"}\n        self.assertEqual(\n            \"/foo/foo\", self.cahandler._accountname_get(url, acme_url, path_dic)\n        )\n\n    def test_103_order_new(self):\n        \"\"\"test order_new\"\"\"\n        acmeclient = Mock()\n        acmeclient.new_order = Mock(return_value=\"new_order\")\n        csr = \"csr\"\n        self.assertEqual(\"new_order\", self.cahandler._order_new(acmeclient, \"csr\"))\n        self.assertTrue(acmeclient.new_order.called)\n        acmeclient.new_order.assert_called_with(csr_pem=\"csr\")\n\n    def test_104_order_new(self):\n        \"\"\"test order_new\"\"\"\n        acmeclient = Mock()\n        acmeclient.new_order = Mock(return_value=\"new_order\")\n        csr = \"csr\"\n        self.cahandler.profile = \"profile\"\n        self.assertEqual(\"new_order\", self.cahandler._order_new(acmeclient, \"csr\"))\n        self.assertTrue(acmeclient.new_order.called)\n        acmeclient.new_order.assert_called_with(csr_pem=\"csr\", profile=\"profile\")\n\n    def test_105_order_new(self):\n        \"\"\"test order_new\"\"\"\n        acmeclient = Mock()\n        acmeclient.new_order.side_effect = [Exception(\"mock_new\"), \"new_order\"]\n        csr = \"csr\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"new_order\",\n                self.cahandler._order_new(acmeclient, \"csr_pem\"),\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to create order: mock_new. Try without profile information.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_url_decode\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    def test_106_revoke_or_fallback(self, mock_cry_load, mock_ossl_load, mock_b64):\n        \"\"\"test _revoke_or_fallback without fallback to OpenSSL crypto load\"\"\"\n        acmeclient = Mock()\n        self.assertFalse(self.cahandler._revoke_or_fallback(acmeclient, \"cert\"))\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_cry_load.called)\n        self.assertFalse(mock_ossl_load.called)\n\n    @patch.object(josepy, \"ComparableX509\", create=True)\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_url_decode\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    def test_107_revoke_or_fallback(\n        self, mock_cry_load, mock_ossl_load, mock_b64, mock_comparable\n    ):\n        \"\"\"test _revoke_or_fallback with fallbnack to OpenSSL crypto load\"\"\"\n        mock_comparable.return_value = \"comparable_cert\"\n        acmeclient = Mock()\n        acmeclient.revoke = Mock(side_effect=[Exception(\"mock_revoke\"), \"foo\"])\n        self.assertFalse(self.cahandler._revoke_or_fallback(acmeclient, \"cert\"))\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_cry_load.called)\n        self.assertTrue(mock_ossl_load.called)\n        self.assertTrue(mock_comparable.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_deprovision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_108_order_issue_success(\n        self,\n        mock_jwk,\n        mock_order,\n        mock_client,\n        mock_pem2der,\n        mock_b64,\n        mock_deprovision,\n    ):\n        \"\"\"test _order_issue with successful order issuance\"\"\"\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_scipt = None\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_b64.return_value = \"mock_b64\"\n        acmeclient = mock_client\n        user_key = mock_jwk\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = (\n            \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\"\n        )\n        order_obj.error = None\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=True)\n        acmeclient.poll_and_finalize.return_value = order_obj\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\",\n                \"mock_b64\",\n            ),\n            self.cahandler._order_issue(acmeclient, user_key, \"csr\"),\n        )\n        self.assertFalse(mock_deprovision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_deprovision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_109_order_issue_success(\n        self,\n        mock_jwk,\n        mock_order,\n        mock_client,\n        mock_pem2der,\n        mock_b64,\n        mock_deprovision,\n    ):\n        \"\"\"test _order_issue with successful order issuance\"\"\"\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_scipt = None\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_b64.return_value = \"mock_b64\"\n        acmeclient = mock_client\n        user_key = mock_jwk\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = (\n            \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\"\n        )\n        order_obj.error = None\n        self.cahandler.dns_update_script = \"mock_dns_update_script\"\n        self.cahandler.acme_sh_script = \"mock_acme_sh_script\"\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=True)\n        acmeclient.poll_and_finalize.return_value = order_obj\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\",\n                \"mock_b64\",\n            ),\n            self.cahandler._order_issue(acmeclient, user_key, \"csr\"),\n        )\n        self.assertTrue(mock_deprovision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_deprovision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_110_order_issue_success(\n        self,\n        mock_jwk,\n        mock_order,\n        mock_client,\n        mock_pem2der,\n        mock_b64,\n        mock_deprovision,\n    ):\n        \"\"\"test _order_issue with successful order issuance\"\"\"\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_scipt = None\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_b64.return_value = \"mock_b64\"\n        acmeclient = mock_client\n        user_key = mock_jwk\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = (\n            \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\"\n        )\n        order_obj.error = None\n        self.cahandler.dns_update_script = \"mock_dns_update_script\"\n        self.cahandler.acme_sh_script = None\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=True)\n        acmeclient.poll_and_finalize.return_value = order_obj\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\",\n                \"mock_b64\",\n            ),\n            self.cahandler._order_issue(acmeclient, user_key, \"csr\"),\n        )\n        self.assertFalse(mock_deprovision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_deprovision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_111_order_issue_success(\n        self,\n        mock_jwk,\n        mock_order,\n        mock_client,\n        mock_pem2der,\n        mock_b64,\n        mock_deprovision,\n    ):\n        \"\"\"test _order_issue with successful order issuance\"\"\"\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_scipt = None\n        mock_pem2der.return_value = \"mock_pem2der\"\n        mock_b64.return_value = \"mock_b64\"\n        acmeclient = mock_client\n        user_key = mock_jwk\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = (\n            \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\"\n        )\n        order_obj.error = None\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_script = \"mock_acme_sh_script\"\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=True)\n        acmeclient.poll_and_finalize.return_value = order_obj\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nfullchain\\n-----END CERTIFICATE-----\",\n                \"mock_b64\",\n            ),\n            self.cahandler._order_issue(acmeclient, user_key, \"csr\"),\n        )\n        self.assertFalse(mock_deprovision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_deprovision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_112_order_issue_no_fullchain(\n        self,\n        mock_jwk,\n        mock_order,\n        mock_client,\n        mock_pem2der,\n        mock_b64,\n        mock_deprovision,\n    ):\n        acmeclient = mock_client\n        user_key = mock_jwk\n        csr_pem = \"dummy_csr\"\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = None\n        order_obj.error = \"Some error\"\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=True)\n        acmeclient.poll_and_finalize.return_value = order_obj\n        self.assertEqual(\n            (\"Error getting certificate: Some error\", None, None),\n            self.cahandler._order_issue(acmeclient, user_key, csr_pem),\n        )\n        self.assertFalse(mock_pem2der.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_deprovision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._order_authorization\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_113_order_issue_invalid_order(self, mock_jwk, mock_order, mock_client):\n        acmeclient = mock_client\n        user_key = mock_jwk\n        csr_pem = \"dummy_csr\"\n        order_obj = MagicMock()\n        order_obj.fullchain_pem = None\n        order_obj.error = None\n        mock_order = False\n        self.cahandler._order_new = MagicMock(return_value=order_obj)\n        self.cahandler._order_authorization = MagicMock(return_value=False)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\n                    \"Order authorization failed. Challenges not answered correctly.\",\n                    None,\n                    None,\n                ),\n                self.cahandler._order_issue(acmeclient, user_key, csr_pem),\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Order authorization failed. Challenges not answered correctly.\",\n            lcm.output,\n        )\n        self.assertFalse(acmeclient.poll_and_finalize.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_provision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_114_order_authorization_http_challenge(\n        self, mock_jwk, mock_order, mock_client, mock_info, mock_provision\n    ):\n        # Setup mocks\n        acmeclient = mock_client\n        user_key = mock_jwk\n        challenge = MagicMock()\n        challenge_name = \"http-01\"\n        challenge_content = \"challenge-content\"\n        challenge.chall.response_and_validation.return_value = (\n            MagicMock(),\n            \"validation\",\n        )\n        challenge.chall.validation.return_value = \"http-01.challenge-token\"\n        challenge.chall.response.return_value = \"response\"\n        challenge.chall.status = \"valid\"\n        authzr = MagicMock()\n        from acme import messages\n\n        authzr.body.challenges = [challenge]\n        authzr.body.identifier.value = \"example.com\"\n        authzr.body.status = messages.STATUS_PENDING\n        challenge.chall = Mock()\n        challenge.chall.response = Mock(return_value=\"response\")\n        mock_info.return_value = (challenge_name, challenge_content, challenge)\n        mock_order.authorizations = [authzr]\n        acmeclient.answer_challenge.return_value = MagicMock()\n        result = self.cahandler._order_authorization(acmeclient, mock_order, user_key)\n        self.assertTrue(result)\n        self.assertFalse(mock_provision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_provision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_115_order_authorization_dns_challenge(\n        self, mock_jwk, mock_order, mock_client, mock_info, mock_provision\n    ):\n        acmeclient = mock_client\n        user_key = mock_jwk\n        challenge = MagicMock()\n        challenge_name = \"dns-challenge\"\n        challenge_content = \"dns-challenge-content\"\n        challenge.chall.response_and_validation.return_value = (\n            MagicMock(),\n            \"validation\",\n        )\n        challenge.chall.response.return_value = \"response\"\n        challenge.chall.status = \"valid\"\n\n        authzr = MagicMock()\n        from acme import messages\n\n        authzr.body.challenges = [challenge]\n        authzr.body.identifier.value = \"example.com\"\n        authzr.body.status = messages.STATUS_PENDING\n        challenge.chall = Mock()\n        challenge.chall.response = Mock(return_value=\"response\")\n\n        cahandler = self.cahandler\n        cahandler.dns_update_script = \"script.sh\"\n        cahandler.acme_sh_script = \"acme.sh\"\n        mock_info.return_value = (challenge_name, challenge_content, challenge)\n        mock_order.authorizations = [authzr]\n        acmeclient.answer_challenge.return_value = MagicMock()\n        result = self.cahandler._order_authorization(acmeclient, mock_order, user_key)\n        self.assertTrue(result)\n        self.assertTrue(mock_provision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_provision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_116_order_authorization_sectigo_email_challenge(\n        self, mock_jwk, mock_order, mock_client, mock_info, mock_provision\n    ):\n        acmeclient = mock_client\n        user_key = mock_jwk\n        challenge = MagicMock()\n        challenge.status = \"valid\"\n        challenge_name = None\n        challenge_content = {\"type\": \"sectigo-email-01\", \"status\": \"valid\"}\n        authzr = MagicMock()\n        from acme import messages\n\n        authzr.body.challenges = [challenge]\n        authzr.body.identifier.value = \"example.com\"\n        authzr.body.status = messages.STATUS_PENDING\n        challenge.chall = Mock()\n        challenge.chall.response = Mock(return_value=\"response\")\n        cahandler = self.cahandler\n        mock_info.return_value = (challenge_name, challenge_content, challenge)\n        mock_order.authorizations = [authzr]\n        acmeclient.answer_challenge.return_value = MagicMock()\n        result = self.cahandler._order_authorization(acmeclient, mock_order, user_key)\n        self.assertTrue(result)\n        self.assertFalse(mock_provision.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._dns_challenge_provision\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_info\")\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.OrderResource\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_117_order_authorization_no_challenge(\n        self, mock_jwk, mock_order, mock_client, mock_info, mock_provision\n    ):\n        acmeclient = mock_client\n        user_key = mock_jwk\n        cahandler = self.cahandler\n        mock_info.return_value = (None, None, None)\n        mock_order.authorizations = [MagicMock()]\n        self.assertFalse(\n            self.cahandler._order_authorization(acmeclient, mock_order, user_key)\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._challenge_filter\")\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_118_get_dns_challenge_success(self, mock_jwk, mock_filter):\n        \"\"\"Test _get_dns_challenge with a valid DNS challenge.\"\"\"\n        challenge = MagicMock()\n        challenge.chall.response_and_validation.return_value = (\n            MagicMock(key_authorization=\"key-auth\"),\n            \"validation\",\n        )\n        authzr = MagicMock()\n        authzr.body.challenges = [challenge]\n        mock_filter.return_value = challenge\n        chall_name, chall_content, result_challenge = self.cahandler._get_dns_challenge(\n            authzr, mock_jwk\n        )\n        self.assertEqual(chall_name, \"dns-challenge\")\n        self.assertEqual(chall_content, \"key-auth\")\n        self.assertEqual(result_challenge, challenge)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.josepy.jwk.JWKRSA\")\n    def test_119_get_dns_challenge_no_challenge(self, mock_jwk):\n        \"\"\"Test _get_dns_challenge with no DNS challenge.\"\"\"\n        authzr = MagicMock()\n        authzr.body.challenges = []\n        # Patch _challenge_filter to return None\n        self.cahandler._challenge_filter = MagicMock(return_value=None)\n        chall_name, chall_content, result_challenge = self.cahandler._get_dns_challenge(\n            authzr, mock_jwk\n        )\n        self.assertIsNone(chall_name)\n        self.assertIsNone(chall_content)\n        self.assertIsNone(result_challenge)\n\n    def test_120_set_environment_variables(self):\n        \"\"\"Test _environment_variables_handle with unset=False.\"\"\"\n        self.cahandler.dns_update_script_variables = {\n            \"TEST_VAR\": \"test_value\",\n            \"PATH\": \"/usr/bin\",\n            \"FORBIDDEN_VAR\": \"should_not_set\",\n        }\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._environment_variables_handle(unset=False)\n        self.assertEqual(os.environ.get(\"TEST_VAR\"), \"test_value\")\n        self.assertNotEqual(os.environ.get(\"PATH\"), \"should_not_set\")\n        self.assertIn(\n            'WARNING:test_a2c:CAhandler._environment_variables_handle(): environment variable \"PATH\" is forbidden and will not be changed',\n            lcm.output,\n        )\n        # Clean up\n        if \"TEST_VAR\" in os.environ:\n            del os.environ[\"TEST_VAR\"]\n\n    def test_121_unset_environment_variables(self):\n        \"\"\"Test _environment_variables_handle with unset=True.\"\"\"\n        self.cahandler.dns_update_script_variables = {\n            \"TEST_VAR\": \"test_value\",\n            \"PATH\": \"/usr/bin\",\n            \"FORBIDDEN_VAR\": \"should_not_set\",\n        }\n        os.environ[\"TEST_VAR\"] = \"test_value\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._environment_variables_handle(unset=True)\n        self.assertIsNone(os.environ.get(\"TEST_VAR\"))\n        self.assertIn(\n            'WARNING:test_a2c:CAhandler._environment_variables_handle(): environment variable \"PATH\" is forbidden and will not be changed',\n            lcm.output,\n        )\n\n    def test_122_unset_not_set_variable(self):\n        \"\"\"Test _environment_variables_handle with unset=True when variable is not set.\"\"\"\n        self.cahandler.dns_update_script_variables = {\n            \"TEST_VAR\": \"test_value\",\n            \"PATH\": \"/usr/bin\",\n            \"FORBIDDEN_VAR\": \"should_not_set\",\n        }\n        if \"TEST_VAR\" in os.environ:\n            del os.environ[\"TEST_VAR\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._environment_variables_handle(unset=True)\n\n        self.assertIn(\n            'WARNING:test_a2c:CAhandler._environment_variables_handle(): environment variable \"PATH\" is forbidden and will not be changed',\n            lcm.output,\n        )\n\n    @patch(\"os.path.exists\")\n    def test_123_dns_update_script_does_not_exist(self, mock_exists):\n        \"\"\"Test _config_dns_update_script_load with dns_update_script that does not exist.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"dns_update_script\": \"/fake/path/script.sh\"}\n        mock_exists.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.cahandler._config_dns_update_script_load(parser)\n        self.assertIsNone(self.cahandler.dns_update_script)\n        self.assertIn(\n            'ERROR:test_a2c:CAhandler._config_dns_update_script_load(): dns update script \"/fake/path/script.sh\" does not exist',\n            lcm.output,\n        )\n\n    @patch(\"os.path.exists\")\n    def test_124_dns_update_script_exists_and_acme_sh_script_missing(self, mock_exists):\n        \"\"\"Test _config_dns_update_script_load with dns_update_script exists but acme_sh_script does not.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"dns_update_script\": \"/fake/path/script.sh\",\n            \"acme_sh_script\": \"/fake/path/acme.sh\",\n            \"dns_update_script_variables\": '{\"VAR1\": \"value1\"}',\n        }\n        mock_exists.side_effect = [True, False]\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.cahandler._config_dns_update_script_load(parser)\n        self.assertEqual(self.cahandler.dns_update_script, \"/fake/path/script.sh\")\n        self.assertIn(\n            'ERROR:test_a2c:CAhandler._config_dns_update_script_load(): acme.sh script \"/fake/path/acme.sh\" does not exist',\n            lcm.output,\n        )\n        self.assertIsNone(self.cahandler.acme_sh_script)\n        self.assertEqual(self.cahandler.dns_update_script_variables, {\"VAR1\": \"value1\"})\n\n    @patch(\"os.path.exists\")\n    def test_125_dns_validation_timeout_parsing(self, mock_exists):\n        \"\"\"Test _config_dns_update_script_load with invalid dns_validation_timeout.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"dns_update_script\": \"/fake/path/script.sh\",\n            \"acme_sh_script\": \"/fake/path/acme.sh\",\n            \"dns_update_script_variables\": '{\"VAR1\": \"value1\"}',\n            \"dns_validation_timeout\": \"not_an_int\",\n        }\n        mock_exists.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            self.cahandler._config_dns_update_script_load(parser)\n        self.assertIn(\n            \"WARNING:test_a2c:CAhandler._config_dns_update_script_load(): Failed to parse dns_validation_timeout parameter: invalid literal for int() with base 10: 'not_an_int'\",\n            lcm.output,\n        )\n        self.assertEqual(self.cahandler.dns_validation_timeout, 20)\n\n    @patch(\"os.path.exists\")\n    def test_126_dns_update_script_variables_none(self, mock_exists):\n        \"\"\"Test _config_dns_update_script_load with dns_update_script_variables as None.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"dns_update_script\": \"/fake/path/script.sh\",\n            \"acme_sh_script\": \"/fake/path/acme.sh\",\n            \"dns_update_script_variables\": \"foo\",\n        }\n        mock_exists.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            self.cahandler._config_dns_update_script_load(parser)\n        self.assertIn(\n            \"WARNING:test_a2c:CAhandler._config_dns_update_script_load(): Failed to parse dns_update_script_variables parameter: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n        self.assertIsNone(self.cahandler.dns_update_script_variables)\n\n    @patch(\"os.path.exists\")\n    def test_127_dns_validation_timeout_parsing(self, mock_exists):\n        \"\"\"Test _config_dns_update_script_load with valid parameters.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"dns_update_script\": \"/fake/path/script.sh\",\n            \"acme_sh_script\": \"/fake/path/acme.sh\",\n            \"dns_update_script_variables\": '{\"VAR1\": \"value1\"}',\n            \"dns_validation_timeout\": \"40\",\n        }\n        mock_exists.return_value = True\n        self.cahandler._config_dns_update_script_load(parser)\n        self.assertEqual(self.cahandler.dns_validation_timeout, 40)\n        self.assertEqual(self.cahandler.dns_update_script, \"/fake/path/script.sh\")\n        self.assertEqual(self.cahandler.acme_sh_script, \"/fake/path/acme.sh\")\n        self.assertEqual(self.cahandler.dns_update_script_variables, {\"VAR1\": \"value1\"})\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._get_http_or_email_challenge\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._get_dns_challenge\")\n    def test_128_challenge_info_dns(\n        self, mock_get_dns_challenge, mock_get_http_or_email_challenge\n    ):\n        \"\"\"Test _challenge_info when dns_update_script is set.\"\"\"\n        self.cahandler.dns_update_script = \"script.sh\"\n        mock_get_dns_challenge.return_value = (\n            \"dns-challenge\",\n            \"key-auth\",\n            \"challenge_obj\",\n        )\n        authzr = MagicMock()\n        user_key = MagicMock()\n\n        chall_name, chall_content, challenge = self.cahandler._challenge_info(\n            authzr, user_key\n        )\n        self.assertEqual(chall_name, \"dns-challenge\")\n        self.assertEqual(chall_content, \"key-auth\")\n        self.assertEqual(challenge, \"challenge_obj\")\n        mock_get_dns_challenge.assert_called_once_with(authzr, user_key)\n        self.assertTrue(mock_get_dns_challenge.called)\n        self.assertFalse(mock_get_http_or_email_challenge.called)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._get_dns_challenge\")\n    @patch(\"examples.ca_handler.acme_ca_handler.CAhandler._get_http_or_email_challenge\")\n    def test_129_challenge_info_http(\n        self, mock_get_http_or_email_challenge, mock_get_dns_challenge\n    ):\n        \"\"\"Test _challenge_info when dns_update_script is not set.\"\"\"\n        self.cahandler.dns_update_script = None\n        mock_get_http_or_email_challenge.return_value = (\n            \"http-challenge\",\n            \"token\",\n            \"challenge_obj\",\n        )\n        authzr = MagicMock()\n        user_key = MagicMock()\n\n        chall_name, chall_content, challenge = self.cahandler._challenge_info(\n            authzr, user_key\n        )\n        self.assertEqual(chall_name, \"http-challenge\")\n        self.assertEqual(chall_content, \"token\")\n        self.assertEqual(challenge, \"challenge_obj\")\n        mock_get_http_or_email_challenge.assert_called_once_with(authzr, user_key)\n        self.assertFalse(mock_get_dns_challenge.called)\n        self.assertTrue(mock_get_http_or_email_challenge.called)\n\n    def test_130_challenge_info_missing_authzr(self):\n        \"\"\"Test _challenge_info when authorization is missing.\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            chall_name, chall_content, challenge = self.cahandler._challenge_info(\n                None, MagicMock()\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:acme authorization is missing\",\n            lcm.output,\n        )\n        self.assertIsNone(chall_name)\n        self.assertIsNone(chall_content)\n        self.assertIsNone(challenge)\n\n    def test_131_challenge_info_missing_user_key(self):\n        \"\"\"Test _challenge_info when user key is missing.\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            chall_name, chall_content, challenge = self.cahandler._challenge_info(\n                MagicMock(), None\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:acme user is missing\",\n            lcm.output,\n        )\n        self.assertIsNone(chall_name)\n        self.assertIsNone(chall_content)\n        self.assertIsNone(challenge)\n\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    def test_132_deprovision_calls_subprocess_and_env(\n        self, mock_basename, mock_splitext, mock_env_handle, mock_subprocess\n    ):\n        \"\"\"Test _dns_challenge_deprovision with subprocess and environment variable handling.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.acme_sh_shell = \"/bin/bash\"\n        self.cahandler.dns_record_dic = {\n            \"test.example.com\": b\"testvalue\",\n            \"other.example.com\": \"othervalue\",\n        }\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.cahandler._dns_challenge_deprovision()\n        self.assertIn(\n            \"DEBUG:test_a2c:CAhandler._dns_challenge_provision(): using shell: /bin/bash\",\n            lcm.output,\n        )\n\n        # Check environment variable handling called for set and unset\n        self.assertEqual(mock_env_handle.call_count, 2)\n        mock_env_handle.assert_any_call(unset=False)\n        mock_env_handle.assert_any_call(unset=True)\n\n        # Check subprocess called for each record\n        self.assertEqual(mock_subprocess.call_count, 2)\n        calls = [call[0][0] for call in mock_subprocess.call_args_list]\n        self.assertTrue(any(\"_rm test.example.com testvalue\" in c for c in calls))\n        self.assertTrue(any(\"_rm other.example.com othervalue\" in c for c in calls))\n\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    def test_133_deprovision_calls_subprocess_and_env(\n        self, mock_basename, mock_splitext, mock_env_handle, mock_subprocess\n    ):\n        \"\"\"Test _dns_challenge_deprovision with subprocess and environment variable handling.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.dns_record_dic = {\n            \"test.example.com\": b\"testvalue\",\n            \"other.example.com\": \"othervalue\",\n        }\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        self.cahandler._dns_challenge_deprovision()\n        # Check environment variable handling called for set and unset\n        self.assertEqual(mock_env_handle.call_count, 2)\n        mock_env_handle.assert_any_call(unset=False)\n        mock_env_handle.assert_any_call(unset=True)\n\n        # Check subprocess called for each record\n        self.assertEqual(mock_subprocess.call_count, 2)\n        calls = [call[0][0] for call in mock_subprocess.call_args_list]\n        self.assertTrue(any(\"_rm test.example.com testvalue\" in c for c in calls))\n        self.assertTrue(any(\"_rm other.example.com othervalue\" in c for c in calls))\n\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    def test_134_deprovision_no_records(self, mock_env_handle, mock_subprocess):\n        \"\"\"Test _dns_challenge_deprovision with no DNS records.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.dns_record_dic = {}\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        self.cahandler._dns_challenge_deprovision()\n        # Should not call subprocess\n        mock_subprocess.assert_not_called()\n        self.assertFalse(mock_env_handle.called)\n\n    def test_135_deprovision_missing_scripts(self):\n        \"\"\"Test _dns_challenge_deprovision with missing scripts.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.acme_sh_shell = \"/bin/bash\"\n        self.cahandler.dns_record_dic = {\n            \"test.example.com\": b\"testvalue\",\n            \"other.example.com\": \"othervalue\",\n        }\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n\n        self.cahandler.dns_update_script = None\n        self.cahandler.acme_sh_script = None\n        self.cahandler.dns_record_dic = {\"test.example.com\": b\"testvalue\"}\n        # Should do nothing (no error)\n        self.cahandler._dns_challenge_deprovision()\n\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    @patch(\"examples.ca_handler.acme_ca_handler.sha256_hash\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_url_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.txt_get\")\n    def test_136_dns_challenge_provision_success(\n        self,\n        mock_txt_get,\n        mock_b64_url_encode,\n        mock_sha256_hash,\n        mock_basename,\n        mock_splitext,\n        mock_env_handle,\n        mock_subprocess,\n        mock_sleep,\n    ):\n        \"\"\"Test _dns_challenge_provision with successful DNS challenge provisioning.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        self.cahandler.dns_validation_timeout = 30\n        fqdn = \"example.com\"\n        key_authorization = \"key-auth\"\n        user_key = MagicMock()\n        mock_sleep.return_value = Mock()\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"hashbytes\"\n        mock_b64_url_encode.return_value = b\"encodedtxt\"\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        # Simulate DNS propagation\n        mock_txt_get.side_effect = [None, b\"encodedtxt\"]\n        self.cahandler._dns_challenge_provision(fqdn, key_authorization, user_key)\n        # Check environment variable handling called for set and unset\n        self.assertEqual(mock_env_handle.call_count, 2)\n        mock_env_handle.assert_any_call(unset=False)\n        mock_env_handle.assert_any_call(unset=True)\n\n        # Check subprocess called\n        mock_subprocess.assert_called()\n        # Check DNS record stored\n        self.assertIn(\"_acme-challenge.example.com\", self.cahandler.dns_record_dic)\n        self.assertEqual(\n            self.cahandler.dns_record_dic[\"_acme-challenge.example.com\"], b\"encodedtxt\"\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    @patch(\"examples.ca_handler.acme_ca_handler.sha256_hash\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_url_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.txt_get\")\n    def test_137_dns_challenge_provision_success(\n        self,\n        mock_txt_get,\n        mock_b64_url_encode,\n        mock_sha256_hash,\n        mock_basename,\n        mock_splitext,\n        mock_env_handle,\n        mock_subprocess,\n        mock_sleep,\n    ):\n        \"\"\"Test _dns_challenge_provision with successful DNS challenge provisioning.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.acme_sh_shell = \"/bin/bash\"\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        self.cahandler.dns_validation_timeout = 10\n        fqdn = \"example.com\"\n        key_authorization = \"key-auth\"\n        user_key = MagicMock()\n        mock_sleep.return_value = Mock()\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"hashbytes\"\n        mock_b64_url_encode.return_value = b\"encodedtxt\"\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        # Simulate DNS propagation\n        mock_txt_get.side_effect = [None, b\"encodedtxt\"]\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.cahandler._dns_challenge_provision(fqdn, key_authorization, user_key)\n        self.assertIn(\n            \"DEBUG:test_a2c:CAhandler._dns_challenge_provision(): using shell: /bin/bash\",\n            lcm.output,\n        )\n\n        # Check environment variable handling called for set and unset\n        self.assertEqual(mock_env_handle.call_count, 2)\n        mock_env_handle.assert_any_call(unset=False)\n        mock_env_handle.assert_any_call(unset=True)\n\n        # Check subprocess called\n        mock_subprocess.assert_called()\n        # Check DNS record stored\n        self.assertIn(\"_acme-challenge.example.com\", self.cahandler.dns_record_dic)\n        self.assertEqual(\n            self.cahandler.dns_record_dic[\"_acme-challenge.example.com\"], b\"encodedtxt\"\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    @patch(\"examples.ca_handler.acme_ca_handler.sha256_hash\")\n    @patch(\"examples.ca_handler.acme_ca_handler.b64_url_encode\")\n    @patch(\"examples.ca_handler.acme_ca_handler.txt_get\")\n    def test_138_dns_challenge_provision_success(\n        self,\n        mock_txt_get,\n        mock_b64_url_encode,\n        mock_sha256_hash,\n        mock_basename,\n        mock_splitext,\n        mock_env_handle,\n        mock_subprocess,\n        mock_sleep,\n    ):\n        \"\"\"Test _dns_challenge_provision with successful DNS challenge provisioning.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.acme_sh_shell = \"/bin/bash\"\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        self.cahandler.dns_validation_timeout = 10\n        fqdn = \"example.com\"\n        key_authorization = \"key-auth\"\n        user_key = MagicMock()\n        mock_sleep.return_value = Mock()\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"hashbytes\"\n        mock_b64_url_encode.return_value = b\"encodedtxt\"\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        # Simulate DNS propagation\n        mock_txt_get.return_value = b\"encodedtxt\"\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.cahandler._dns_challenge_provision(fqdn, key_authorization, user_key)\n        self.assertIn(\n            \"DEBUG:test_a2c:CAhandler._dns_challenge_provision(): using shell: /bin/bash\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:_dns_challenge_provision(): found txt record in DNS\",\n            lcm.output,\n        )\n        # Check environment variable handling called for set and unset\n        self.assertEqual(mock_env_handle.call_count, 2)\n        mock_env_handle.assert_any_call(unset=False)\n        mock_env_handle.assert_any_call(unset=True)\n\n        # Check subprocess called\n        mock_subprocess.assert_called()\n        # Check DNS record stored\n        self.assertIn(\"_acme-challenge.example.com\", self.cahandler.dns_record_dic)\n        self.assertEqual(\n            self.cahandler.dns_record_dic[\"_acme-challenge.example.com\"], b\"encodedtxt\"\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"subprocess.call\")\n    @patch(\n        \"examples.ca_handler.acme_ca_handler.CAhandler._environment_variables_handle\"\n    )\n    @patch(\"os.path.splitext\")\n    @patch(\"os.path.basename\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_139_dns_challenge_provision_timeout(\n        self,\n        mock_txt_get,\n        mock_b64_url_encode,\n        mock_sha256_hash,\n        mock_basename,\n        mock_splitext,\n        mock_env_handle,\n        mock_subprocess,\n        mock_sleep,\n    ):\n        \"\"\"Test _dns_challenge_provision with DNS propagation timeout.\"\"\"\n        self.cahandler.dns_update_script = \"/tmp/dns_update.sh\"\n        self.cahandler.acme_sh_script = \"/tmp/acme.sh\"\n        self.cahandler.acme_sh_shell = \"/bin/bash\"\n        self.cahandler.dns_update_script_variables = {\"TEST_VAR\": \"value\"}\n        self.cahandler.dns_validation_timeout = 10\n        fqdn = \"example.com\"\n        key_authorization = \"key-auth\"\n        user_key = MagicMock()\n        mock_sleep.return_value = Mock()\n        mock_sha256_hash.return_value = b\"hashbytes\"\n        mock_b64_url_encode.return_value = b\"encodedtxt\"\n        mock_basename.return_value = \"dns_update.sh\"\n        mock_splitext.side_effect = lambda x: (\"/tmp/dns_update\", \".sh\")\n        mock_subprocess.return_value = 0\n        # Simulate DNS never propagates\n        mock_txt_get.return_value = None\n\n        self.cahandler._dns_challenge_provision(fqdn, key_authorization, user_key)\n\n        # Should still store the record\n        self.assertIn(\"_acme-challenge.example.com\", self.cahandler.dns_record_dic)\n        self.assertEqual(\n            self.cahandler.dns_record_dic[\"_acme-challenge.example.com\"],\n            b\"4EsbamPacNncn5UI7noRUSqV4bk-1xyk8dpPgpQisJY\",\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Registration\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Directory\")\n    def test_140_existing_account_found(self, mock_directory, mock_reg, mock_client):\n        \"\"\"Test _registration_lookup with existing account found.\"\"\"\n        self.cahandler.acme_url = \"https://acme.example.com\"\n        self.cahandler.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        self.cahandler.account = \"12345\"\n        regr = MagicMock()\n        regr.uri = \"https://acme.example.com/acme/acct/12345\"\n        mock_client.query_registration.return_value = regr\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.cahandler._registration_lookup(\n                mock_client, mock_reg, mock_directory, MagicMock()\n            )\n        self.assertEqual(result, regr)\n        self.assertIn(\n            \"INFO:test_a2c:Found existing account: https://acme.example.com/acme/acct/12345\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Registration\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Directory\")\n    def test_141_account_not_found_register_new(\n        self, mock_directory, mock_reg, mock_client\n    ):\n        \"\"\"Test _registration_lookup when account is not found and needs to be registered.\"\"\"\n        self.cahandler.acme_url = \"https://acme.example.com\"\n        self.cahandler.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        self.cahandler.account = \"12345\"\n        regr = MagicMock()\n        delattr(regr, \"uri\")\n        mock_client.query_registration.return_value = regr\n\n        # Patch _account_register to return a new regr with uri\n        new_regr = MagicMock()\n        new_regr.uri = \"https://acme.example.com/acme/acct/67890\"\n        self.cahandler._account_register = MagicMock(return_value=new_regr)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.cahandler._registration_lookup(\n                mock_client, mock_reg, mock_directory, MagicMock()\n            )\n        self.assertEqual(result, new_regr)\n        self.assertIn(\n            \"ERROR:test_a2c:Account lookup failed. Account 12345 not found. Trying to register new account.\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"INFO:test_a2c:New account: https://acme.example.com/acme/acct/67890\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.acme_ca_handler.client.ClientV2\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Registration\")\n    @patch(\"examples.ca_handler.acme_ca_handler.messages.Directory\")\n    def test_142_no_account_set_register_new(\n        self, mock_directory, mock_reg, mock_client\n    ):\n        \"\"\"Test _registration_lookup when no account is set and needs to be registered.\"\"\"\n        self.cahandler.acme_url = \"https://acme.example.com\"\n        self.cahandler.path_dic = {\"acct_path\": \"/acme/acct/\"}\n        self.cahandler.account = \"12345\"\n        # Remove account\n        self.cahandler.account = None\n        # Patch _account_register to return a new regr with uri\n        new_regr = MagicMock()\n        new_regr.uri = \"https://acme.example.com/acme/acct/99999\"\n        self.cahandler._account_register = MagicMock(return_value=new_regr)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.cahandler._registration_lookup(\n                mock_client, mock_reg, mock_directory, MagicMock()\n            )\n        self.assertEqual(result, new_regr)\n        self.assertIn(\n            \"INFO:test_a2c:New account: https://acme.example.com/acme/acct/99999\",\n            lcm.output,\n        )\n\n    def test_142_jwk_strip_minimal_fields(self):\n        \"\"\"Test _jwk_strip returns minimal JWK for RSA key\"\"\"\n        user_key = self._generate_full_jwk()\n        stripped_key = self.cahandler._jwk_strip(user_key)\n        self.assertIsInstance(stripped_key, josepy.JWKRSA)\n        minimal_jwk = stripped_key.to_json()\n        self.assertIn(\"kty\", minimal_jwk)\n        self.assertIn(\"n\", minimal_jwk)\n        self.assertIn(\"e\", minimal_jwk)\n        self.assertEqual(len(minimal_jwk), 3)  # Only minimal fields\n\n    def test_143_jwk_strip_non_rsa_key(self):\n        \"\"\"Test _jwk_strip returns original key if not RSA\"\"\"\n        user_key = self._generate_full_jwk()\n        with patch.object(\n            type(user_key),\n            \"to_json\",\n            return_value={\"kty\": \"EC\", \"crv\": \"P-256\", \"x\": \"foo\", \"y\": \"bar\"},\n        ):\n            result = self.cahandler._jwk_strip(user_key)\n            self.assertEqual(result, user_key)\n\n    def test_144_jwk_strip_missing_fields(self):\n        \"\"\"Test _jwk_strip returns None if required fields are missing\"\"\"\n        user_key = self._generate_full_jwk()\n        with patch.object(\n            type(user_key), \"to_json\", return_value={\"kty\": \"RSA\", \"e\": \"AQAB\"}\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n                result = self.cahandler._jwk_strip(user_key)\n            self.assertIn(\n                \"ERROR:test_a2c:Missing required JWK fields for RSA key: n\", lcm.output\n            )\n            self.assertIsNone(result)\n\n    def test_145_jwk_strip_invalid_jwk(self):\n        \"\"\"Test _jwk_strip handles exception when reconstructing JWKRSA\"\"\"\n        user_key = self._generate_full_jwk()\n        with patch.object(\n            type(user_key), \"to_json\", return_value={\"kty\": \"RSA\", \"n\": None, \"e\": None}\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n                result = self.cahandler._jwk_strip(user_key)\n            self.assertIn(\n                \"ERROR:test_a2c:Failed to strip JWK to minimal fields. Input: {'kty': 'RSA', 'n': None, 'e': None}, Error: 'NoneType' object has no attribute 'encode'\",\n                lcm.output,\n            )\n            self.assertIsNone(result)\n\n    @patch(\"examples.ca_handler.acme_ca_handler.handler_config_check\")\n    def test_146_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_acmechallenge.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nfrom unittest.mock import patch, MagicMock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.acmechallenge import Acmechallenge\n\n        self.acmechallenge = Acmechallenge(False, None, self.logger)\n\n    def test_001__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.acmechallenge.__enter__()\n\n    def test_002__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.acmechallenge.__exit__()\n\n    def test_003_lookup(self):\n        \"\"\"test lookup without pathinfo\"\"\"\n        path_info = None\n        self.assertFalse(self.acmechallenge.lookup(path_info))\n\n    def test_004_lookup(self):\n        \"\"\"test lookup strange token returning wrong data\"\"\"\n        path_info = \"foo\"\n        self.acmechallenge.dbstore.cahandler_lookup.return_value = \"lookup\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.acmechallenge.lookup(path_info))\n        self.assertIn(\"INFO:test_a2c:Lookup token: foo\", lcm.output)\n\n    def test_005_lookup(self):\n        \"\"\"test lookup strange token rest replace\"\"\"\n        path_info = \"/.well-known/acme-challenge/foo1\"\n        self.acmechallenge.dbstore.cahandler_lookup.return_value = \"lookup\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.acmechallenge.lookup(path_info))\n        self.assertIn(\"INFO:test_a2c:Lookup token: foo1\", lcm.output)\n\n    def test_006_lookup(self):\n        \"\"\"test lookup strange token rest replace\"\"\"\n        path_info = \"/.well-known/acme-challenge/foo\"\n        self.acmechallenge.dbstore.cahandler_lookup.return_value = {\n            \"value1\": \"key_authorization\"\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"key_authorization\", self.acmechallenge.lookup(path_info))\n        self.assertIn(\"INFO:test_a2c:Lookup token: foo\", lcm.output)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_asa_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, Mock, MagicMock\nimport requests\nimport base64\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.asa_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n        self.maxDiff = None\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    def test_003_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_004_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_005_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"api_host\": \"api_host\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_006_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"api_user\": \"api_user\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_007_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"api_password\": \"api_password\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_008_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"api_key\": \"api_key\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertEqual(\"api_key\", self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_009_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\n            \"CAhandler\": {\n                \"api_host\": \"api_host\",\n                \"api_user\": \"api_user\",\n                \"api_password\": \"api_password\",\n                \"api_key\": \"api_key\",\n            }\n        }\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertEqual(\"api_key\", self.cahandler.api_key)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_010_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_011_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"request_timeout\": 20}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_012_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"request_timeout\": \"aa\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:request_timeout parameter is not an integer. Error: invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_013_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"cert_validity_days\": 10}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        # self.assertIn('ERROR:test_a2c:CAhandler._config_load(): request_timeout not an integer', lcm.output)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(10, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_014_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"cert_validity_days\": \"aa\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_015_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"ca_bundle\": \"aa\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"aa\", self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_016_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"CAhandler\": {\"ca_bundle\": \"False\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.load_config\")\n    def test_017_config_load(self, mock_config_load):\n        \"\"\"test _config_load\"\"\"\n        mock_config_load.return_value = {\"Order\": {\"header_info_list\": '[\"foo\"]'}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_host has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_user has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_key has not been not set\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete. Variable api_password has not been not set\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(30, self.cahandler.cert_validity_days)\n        self.assertEqual(\"foo\", self.cahandler.header_info_field)\n\n    @patch.object(requests, \"post\")\n    def test_018__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_post(\"url\", \"data\")\n        )\n\n    @patch(\"requests.post\")\n    def test_019__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_post(\"url\", \"data\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not parse the response for an API post() request: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_020__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.text = None\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_021__api_post(self, mock_req):\n        \"\"\"test _api_post(=\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_post\"), self.cahandler._api_post(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:API post() request returned an error: exc_api_post\",\n            lcm.output,\n        )\n\n    @patch.object(requests, \"get\")\n    def test_022__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_get(\"url\")\n        )\n\n    @patch(\"requests.get\")\n    def test_023__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_get(\"url\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not parse the response for an API get() request: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    def test_024__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((500, \"exc_api_get\"), self.cahandler._api_get(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API get() request returned error: exc_api_get\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_get\")\n    def test_025__issuers_list(self, mock_get):\n        \"\"\"test _issuers_list()\"\"\"\n        mock_get.return_value = (200, \"content\")\n        self.assertEqual(\"content\", self.cahandler._issuers_list())\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_get\")\n    def test_026__profiles_list(self, mock_get):\n        \"\"\"test _profiles_list()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_get.return_value = (200, \"content\")\n        self.assertEqual(\"content\", self.cahandler._profiles_list())\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_get\")\n    def test_027__certificates_list(self, mock_get):\n        \"\"\"test _profiles_list()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_get.return_value = (200, \"content\")\n        self.assertEqual(\"content\", self.cahandler._certificates_list())\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    def test_028_cert_status_get(self, mock_req):\n        \"\"\"test _profiles_list()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_req.return_value = (\"status_code\", {\"foo\": \"bar\"})\n        self.assertEqual(\n            {\"foo\": \"bar\", \"code\": \"status_code\"},\n            self.cahandler._cert_status_get(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_cn_get\")\n    def test_029__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_san.return_value = [\"san0\", \"san1\"]\n        self.assertEqual(\"cn\", self.cahandler._csr_cn_get(\"csr\"))\n        self.assertFalse(mock_san.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_cn_get\")\n    def test_030__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"dns:san0\", \"dns:san1\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"san0\", self.cahandler._csr_cn_get(\"csr\"))\n        self.assertIn(\"INFO:test_a2c:CN not found in CSR\", lcm.output)\n        self.assertIn(\n            \"INFO:test_a2c:CN not found in CSR. Using first SAN entry as CN: san0\",\n            lcm.output,\n        )\n        self.assertTrue(mock_san.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_cn_get\")\n    def test_031__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._csr_cn_get(\"csr\"))\n        self.assertIn(\"INFO:test_a2c:CN not found in CSR\", lcm.output)\n        self.assertIn(\n            \"ERROR:test_a2c:CN not found in CSR. No SAN entries found\",\n            lcm.output,\n        )\n        self.assertTrue(mock_san.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuers_list\")\n    def test_032_issuer_verify(self, mock_list):\n        \"\"\"_issuer_verify()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_list.return_value = {\"issuers\": [\"1\", \"2\", \"ca_name\"]}\n        self.assertFalse(self.cahandler._issuer_verify())\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuers_list\")\n    def test_033_issuer_verify(self, mock_list):\n        \"\"\"_issuer_verify()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_list.return_value = {\"issuers\": [\"1\", \"2\", \"3\"]}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"CA ca_name not found\", self.cahandler._issuer_verify())\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler.enroll(): CA ca_name not found\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuers_list\")\n    def test_034_issuer_verify(self, mock_list):\n        \"\"\"_issuer_verify()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_list.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"Malformed response\", self.cahandler._issuer_verify())\n        self.assertIn(\n            'ERROR:test_a2c:Malformed response. \"issuers\" key not found',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profiles_list\")\n    def test_035_profile_verify(self, mock_list):\n        \"\"\"_profile_verify()\"\"\"\n        self.cahandler.profile_name = \"profile_name\"\n        mock_list.return_value = {\"profiles\": [\"1\", \"2\", \"profile_name\"]}\n        self.assertFalse(self.cahandler._profile_verify())\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profiles_list\")\n    def test_036_profile_verify(self, mock_list):\n        \"\"\"_profile_verify()\"\"\"\n        self.cahandler.profile_name = \"profile_name\"\n        mock_list.return_value = {\"profiles\": [\"1\", \"2\", \"3\"]}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"Profile profile_name not found\", self.cahandler._profile_verify()\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Profile profile_name not found\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profiles_list\")\n    def test_037_profile_verify(self, mock_list):\n        \"\"\"_profile_verify()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_list.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"Malformed response\", self.cahandler._profile_verify())\n        self.assertIn(\n            'ERROR:test_a2c:Malformed response. \"profiles\" key not found',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.asa_ca_handler.uts_to_date_utc\")\n    @patch(\"examples.ca_handler.asa_ca_handler.uts_now\")\n    def test_038__validity_dates_get(self, mock_now, mock_utc):\n        \"\"\"test _validity_dates_get()\"\"\"\n        mock_now.return_value = 10\n        mock_utc.side_effect = [\"date1\", \"date2\"]\n        self.assertEqual((\"date1\", \"date2\"), self.cahandler._validity_dates_get())\n        self.assertTrue(mock_now.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    def test_039__pem_cert_chain_generate(self, mock_dec, mock_d2p, mock_b2s):\n        \"\"\"test _pem_cert_chain_generate()\"\"\"\n        mock_b2s.return_value = \"cert\"\n        self.assertEqual(\n            \"certcert\", self.cahandler._pem_cert_chain_generate([\"cert\", \"chain\"])\n        )\n\n    def test_040__pem_cert_chain_generate(self):\n        \"\"\"test _pem_cert_chain_generate()\"\"\"\n        cert_list = [\n            \"MIIF7DCCBFSgAwIBAgIKB/8cQ9wAI3UbITANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJERTERMA8GA1UECgwIT3BlblhQS0kxDDAKBgNVBAsMA1BLSTEqMCgGA1UEAwwhT3BlblhQS0kgRGVtbyBJc3N1aW5nIENBIDIwMjMwMjA0MB4XDTIzMDIwNTA2NDY0MloXDTI0MDIwNTA2NDY0MlowazETMBEGCgmSJomT8ixkARkWA29yZzEYMBYGCgmSJomT8ixkARkWCE9wZW5YUEtJMR8wHQYKCZImiZPyLGQBGRYPVGVzdCBEZXBsb3ltZW50MRkwFwYDVQQDDBBhY21lMS5keW5hbW9wLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAre1jtb8Xjqr49QH3fWe2kH+yDk3NXfxHyOmKcNcBke68WMRB5Irrdj15JfAsXxu9psLVEOJgvdOLOnUbhN57uBLHwMAC1LH6HruYuCqtbaSezgJIYIEACvtQmIy6BIvigqwX31eLkA7kk7YXeJCnvrr461t/uZkhmaXZM9+G4asSj6fT0ffA7OVVqewDdE+d2VgCjPlH9uqPMOVK2m/AQj+jEVV/IV2znngZmkAsmYi6h2Wg08vEzTMyvhZIEma3xo6M9g9VIsTQP/ETxxhAAgzEQ0Jlz90rOioZK7mkx8xH1fLlhyfX53vqcEbva5evy1YMGEs0XZPYu2B6Oya9WQIDAQABo4ICITCCAh0wgYcGCCsGAQUFBwEBBHsweTBRBggrBgEFBQcwAoZFaHR0cDovL3BraS5leGFtcGxlLmNvbS9kb3dubG9hZC9PcGVuWFBLSV9EZW1vX0lzc3VpbmdfQ0FfMjAyMzAyMDQuY2VyMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wHwYDVR0jBBgwFoAU0f8PWcniVXltJeA6q7wYtyJrNFAwDAYDVR0TAQH/BAIwADBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vcGtpLmV4YW1wbGUuY29tL2Rvd25sb2FkL09wZW5YUEtJX0RlbW9fSXNzdWluZ19DQV8yMDIzMDIwNC5jcmwwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDgYDVR0PAQH/BAQDAgWgMIGoBgNVHSAEgaAwgZ0wgZoGAyoDBDCBkjArBggrBgEFBQcCARYfaHR0cDovL3BraS5leGFtcGxlLmNvbS9jcHMuaHRtbDArBggrBgEFBQcCARYfaHR0cDovL3BraS5leGFtcGxlLmNvbS9jcHMuaHRtbDA2BggrBgEFBQcCAjAqGihUaGlzIGlzIGEgY29tbWVudCBmb3IgcG9saWN5IG9pZCAxLjIuMy40MBsGA1UdEQQUMBKCEGFjbWUxLmR5bmFtb3AuZGUwHQYDVR0OBBYEFA3AUTV0pg0fsd3Cd6/BskOEB9MVMA0GCSqGSIb3DQEBCwUAA4IBgQB0xnnl6BJDXrbTQr7TdkRPmcCDFUmi8aVTYozbQ8EKxIYEPsfzxOFbSG/wn+4Sjz7HqvzqxyisfTopqWrvpqIhlXOEFMnNYTDO4LzCd81Dcs4czjoIRxRTisgNCvWR9hbeH9HzdRT1UF/c4VxxLEONSsGHksoXa+G4u7XmPwD4dTUIP49Mmj2a28z/viG8KftcjAEo1S7OB+/xyPeVDYrgagMR31a69pI+yuQa0J66O/LJQrzjWf6wHToQErQPcEBtDxY2wx3hROMtdla9lUEU8XLb3e9zByZwOfDhFpw8iYkJx/BUZlsmIKaZSpYVS+0D5LI1R5PENhT/2gRxaA31RiNLK/E8CSU7MMadqImkFLkDHU2x+2SRENwvoOEUAOewjVlhB1pK0r5WEye2lBjl8cUa+8qhIrAOqggApQ7eCQq7v2bL08VxKz5baOhKfLZ9u4MH6q52pnqXmll0W7JXrJSbam5r3YoSelm94VwVyaSkfd+LT4YMAP7GDDvtT6Y=\"\n        ]\n        result = \"\"\"-----BEGIN CERTIFICATE-----\nMIIF7DCCBFSgAwIBAgIKB/8cQ9wAI3UbITANBgkqhkiG9w0BAQsFADBaMQswCQYD\nVQQGEwJERTERMA8GA1UECgwIT3BlblhQS0kxDDAKBgNVBAsMA1BLSTEqMCgGA1UE\nAwwhT3BlblhQS0kgRGVtbyBJc3N1aW5nIENBIDIwMjMwMjA0MB4XDTIzMDIwNTA2\nNDY0MloXDTI0MDIwNTA2NDY0MlowazETMBEGCgmSJomT8ixkARkWA29yZzEYMBYG\nCgmSJomT8ixkARkWCE9wZW5YUEtJMR8wHQYKCZImiZPyLGQBGRYPVGVzdCBEZXBs\nb3ltZW50MRkwFwYDVQQDDBBhY21lMS5keW5hbW9wLmRlMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAre1jtb8Xjqr49QH3fWe2kH+yDk3NXfxHyOmKcNcB\nke68WMRB5Irrdj15JfAsXxu9psLVEOJgvdOLOnUbhN57uBLHwMAC1LH6HruYuCqt\nbaSezgJIYIEACvtQmIy6BIvigqwX31eLkA7kk7YXeJCnvrr461t/uZkhmaXZM9+G\n4asSj6fT0ffA7OVVqewDdE+d2VgCjPlH9uqPMOVK2m/AQj+jEVV/IV2znngZmkAs\nmYi6h2Wg08vEzTMyvhZIEma3xo6M9g9VIsTQP/ETxxhAAgzEQ0Jlz90rOioZK7mk\nx8xH1fLlhyfX53vqcEbva5evy1YMGEs0XZPYu2B6Oya9WQIDAQABo4ICITCCAh0w\ngYcGCCsGAQUFBwEBBHsweTBRBggrBgEFBQcwAoZFaHR0cDovL3BraS5leGFtcGxl\nLmNvbS9kb3dubG9hZC9PcGVuWFBLSV9EZW1vX0lzc3VpbmdfQ0FfMjAyMzAyMDQu\nY2VyMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wHwYDVR0j\nBBgwFoAU0f8PWcniVXltJeA6q7wYtyJrNFAwDAYDVR0TAQH/BAIwADBWBgNVHR8E\nTzBNMEugSaBHhkVodHRwOi8vcGtpLmV4YW1wbGUuY29tL2Rvd25sb2FkL09wZW5Y\nUEtJX0RlbW9fSXNzdWluZ19DQV8yMDIzMDIwNC5jcmwwEwYDVR0lBAwwCgYIKwYB\nBQUHAwEwDgYDVR0PAQH/BAQDAgWgMIGoBgNVHSAEgaAwgZ0wgZoGAyoDBDCBkjAr\nBggrBgEFBQcCARYfaHR0cDovL3BraS5leGFtcGxlLmNvbS9jcHMuaHRtbDArBggr\nBgEFBQcCARYfaHR0cDovL3BraS5leGFtcGxlLmNvbS9jcHMuaHRtbDA2BggrBgEF\nBQcCAjAqGihUaGlzIGlzIGEgY29tbWVudCBmb3IgcG9saWN5IG9pZCAxLjIuMy40\nMBsGA1UdEQQUMBKCEGFjbWUxLmR5bmFtb3AuZGUwHQYDVR0OBBYEFA3AUTV0pg0f\nsd3Cd6/BskOEB9MVMA0GCSqGSIb3DQEBCwUAA4IBgQB0xnnl6BJDXrbTQr7TdkRP\nmcCDFUmi8aVTYozbQ8EKxIYEPsfzxOFbSG/wn+4Sjz7HqvzqxyisfTopqWrvpqIh\nlXOEFMnNYTDO4LzCd81Dcs4czjoIRxRTisgNCvWR9hbeH9HzdRT1UF/c4VxxLEON\nSsGHksoXa+G4u7XmPwD4dTUIP49Mmj2a28z/viG8KftcjAEo1S7OB+/xyPeVDYrg\nagMR31a69pI+yuQa0J66O/LJQrzjWf6wHToQErQPcEBtDxY2wx3hROMtdla9lUEU\n8XLb3e9zByZwOfDhFpw8iYkJx/BUZlsmIKaZSpYVS+0D5LI1R5PENhT/2gRxaA31\nRiNLK/E8CSU7MMadqImkFLkDHU2x+2SRENwvoOEUAOewjVlhB1pK0r5WEye2lBjl\n8cUa+8qhIrAOqggApQ7eCQq7v2bL08VxKz5baOhKfLZ9u4MH6q52pnqXmll0W7JX\nrJSbam5r3YoSelm94VwVyaSkfd+LT4YMAP7GDDvtT6Y=\n-----END CERTIFICATE-----\n\"\"\"\n        self.assertEqual(result, self.cahandler._pem_cert_chain_generate(cert_list))\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._pem_cert_chain_generate\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_get\")\n    def test_041___issuer_chain_get(self, mock_req, mock_pem):\n        \"\"\"test _issuer_chain_get()\"\"\"\n        mock_req.return_value = (\"code\", {\"certs\": [\"bar\", \"foo\"]})\n        mock_pem.return_value = \"issuer_chain\"\n        self.cahandler.ca_name = \"ca_name\"\n        self.assertEqual(\"issuer_chain\", self.cahandler._issuer_chain_get())\n        self.assertTrue(mock_pem.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._pem_cert_chain_generate\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_get\")\n    def test_042___issuer_chain_get(self, mock_req, mock_pem):\n        \"\"\"test _issuer_chain_get()\"\"\"\n        mock_req.return_value = (\"code\", {\"foobar\": [\"bar\", \"foo\"]})\n        mock_pem.return_value = \"issuer_chain\"\n        self.cahandler.ca_name = \"ca_name\"\n        self.assertFalse(self.cahandler._issuer_chain_get())\n        self.assertFalse(mock_pem.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.eab_profile_header_info_check\")\n    def test_043_enroll(\n        self,\n        mock_pv,\n        mock_iv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n        mock_ecl,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = \"pv_error\"\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (200, \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.cahandler.header_info_field = \"foo\"\n        self.assertEqual((\"pv_error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_pv.called)\n        self.assertFalse(mock_iv.called)\n        self.assertFalse(mock_icg.called)\n        self.assertFalse(mock_cpg.called)\n        self.assertFalse(mockccg.called)\n        self.assertFalse(mock_vdg.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_044_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n        mock_ecl,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = None\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (200, \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.cahandler.header_info_field = \"foo\"\n        self.cahandler.enrollment_config_log = True\n        self.assertEqual(\n            (None, \"bcertissuer_chain\", \"cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_iv.called)\n        self.assertTrue(mock_pv.called)\n        self.assertTrue(mock_icg.called)\n        self.assertTrue(mock_cpg.called)\n        self.assertTrue(mockccg.called)\n        self.assertTrue(mock_vdg.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_b2s.called)\n        self.assertTrue(mock_d2p.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_045_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = None\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (200, \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.assertEqual(\n            (None, \"bcertissuer_chain\", \"cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_iv.called)\n        self.assertTrue(mock_pv.called)\n        self.assertTrue(mock_icg.called)\n        self.assertTrue(mock_cpg.called)\n        self.assertTrue(mockccg.called)\n        self.assertTrue(mock_vdg.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_b2s.called)\n        self.assertTrue(mock_d2p.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_046_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = \"mock_iv\"\n        mock_pv.return_value = None\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (\"code\", \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.assertEqual((\"mock_iv\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_iv.called)\n        self.assertFalse(mock_pv.called)\n        self.assertFalse(mock_icg.called)\n        self.assertFalse(mock_cpg.called)\n        self.assertFalse(mockccg.called)\n        self.assertFalse(mock_vdg.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_d2p.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_047_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = \"mock_pv\"\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (\"code\", \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.assertEqual((\"mock_pv\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_iv.called)\n        self.assertTrue(mock_pv.called)\n        self.assertFalse(mock_icg.called)\n        self.assertFalse(mock_cpg.called)\n        self.assertFalse(mockccg.called)\n        self.assertFalse(mock_vdg.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_d2p.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_048_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = None\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (500, \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        self.assertEqual(\n            (\"Enrollment failed\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_iv.called)\n        self.assertTrue(mock_pv.called)\n        self.assertTrue(mock_icg.called)\n        self.assertTrue(mock_cpg.called)\n        self.assertTrue(mockccg.called)\n        self.assertTrue(mock_vdg.called)\n        self.assertFalse(mock_b64.called)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_d2p.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.asa_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_chain_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._profile_verify\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._issuer_verify\")\n    def test_049_enroll(\n        self,\n        mock_iv,\n        mock_pv,\n        mock_icg,\n        mock_cpg,\n        mockccg,\n        mock_vdg,\n        mock_b64,\n        mock_d2p,\n        mock_b2s,\n        mock_post,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_iv.return_value = None\n        mock_pv.return_value = None\n        mock_icg.return_value = \"issuer_chain\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        mock_post.return_value = (500, \"cert\")\n        mock_b2s.return_value = \"bcert\"\n        mock_cpg.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Enrollment failed\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not extract the public key from CSR\",\n            lcm.output,\n        )\n        self.assertTrue(mock_iv.called)\n        self.assertTrue(mock_pv.called)\n        self.assertTrue(mock_icg.called)\n        self.assertTrue(mock_cpg.called)\n        self.assertFalse(mockccg.called)\n        self.assertFalse(mock_vdg.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_d2p.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_ski_get\")\n    def test_050_revoke(self, mock_ski, mock_post, mock_epr):\n        \"\"\"test revoke()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_ski.return_value = \"serial\"\n        mock_post.return_value = (\"code\", None)\n        self.assertEqual((\"code\", None, None), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_ski.called)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_epr.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_ski_get\")\n    def test_051_revoke(self, mock_ski, mock_post, mock_epr):\n        \"\"\"test revoke()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_ski.return_value = \"serial\"\n        mock_post.return_value = (\"code\", None)\n        self.cahandler.eab_profiling = True\n        self.assertEqual((\"code\", None, None), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_ski.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_epr.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_ski_get\")\n    def test_052_revoke(self, mock_ski, mock_post):\n        \"\"\"test revoke()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_ski.return_value = \"mock_ski\"\n        mock_post.return_value = (\"code\", {\"message\": \"message\"})\n        self.assertEqual(\n            (\"code\", \"urn:ietf:params:acme:error:serverInternal\", \"message\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_ski.called)\n        self.assertTrue(mock_post.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_ski_get\")\n    def test_053_revoke(self, mock_ski, mock_post):\n        \"\"\"test revoke()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_ski.return_value = \"ski\"\n        mock_post.return_value = (\"code\", {\"Message\": \"Message\"})\n        self.assertEqual(\n            (\"code\", \"urn:ietf:params:acme:error:serverInternal\", \"Message\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_ski.called)\n        self.assertTrue(mock_post.called)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.asa_ca_handler.cert_ski_get\")\n    def test_054_revoke(self, mock_ski, mock_post):\n        \"\"\"test revoke()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        mock_ski.return_value = \"ski\"\n        mock_post.return_value = (\"code\", {\"foo\": \"bar\"})\n        self.assertEqual(\n            (\"code\", \"urn:ietf:params:acme:error:serverInternal\", \"Unknown error\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_ski.called)\n        self.assertTrue(mock_post.called)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    def test_055_config_user_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        config_dic = {\"api_user_variable\": \"api_user_var\"}\n        self.cahandler._config_user_load(config_dic)\n        self.assertEqual(\"user_var\", self.cahandler.api_user)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    def test_056_config_user_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        config_dic = {\"api_user_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_user_load(config_dic)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load user_variable: does_not_exist\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    def test_057_config_user_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        config_dic = {\"api_user_variable\": \"api_user_var\", \"api_user\": \"api_user\"}\n        self.cahandler._config_user_load(config_dic)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        # self.assertIn(\"foo\", lcm.output)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_host_var\": \"host_var\"})\n    def test_058_config_host_load(self):\n        \"\"\"test _config_load - load template with host variable\"\"\"\n        config_dic = {\"api_host_variable\": \"api_host_var\"}\n        self.cahandler._config_host_load(config_dic)\n        self.assertEqual(\"host_var\", self.cahandler.api_host)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_host_var\": \"host_var\"})\n    def test_059_config_host_load(self):\n        \"\"\"test _config_load - load template with host variable\"\"\"\n        config_dic = {\"api_host_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_host_load(config_dic)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load host_variable: does_not_exist\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_host_var\": \"host_var\"})\n    def test_060_config_host_load(self):\n        \"\"\"test _config_load - load template with host variable\"\"\"\n        config_dic = {\"api_host_variable\": \"api_host_var\", \"api_host\": \"api_host\"}\n        self.cahandler._config_host_load(config_dic)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        # self.assertIn(\"foo\", lcm.output)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_key_var\": \"key_var\"})\n    def test_061_config_key_load(self):\n        \"\"\"test _config_load - load template with key variable\"\"\"\n        config_dic = {\"api_key_variable\": \"api_key_var\"}\n        self.cahandler._config_key_load(config_dic)\n        self.assertEqual(\"key_var\", self.cahandler.api_key)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_key_var\": \"key_var\"})\n    def test_062_config_key_load(self):\n        \"\"\"test _config_load - load template with key variable\"\"\"\n        config_dic = {\"api_key_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_key_load(config_dic)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load key_variable: does_not_exist\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_key_var\": \"key_var\"})\n    def test_063_config_key_load(self):\n        \"\"\"test _config_load - load template with key variable\"\"\"\n        config_dic = {\"api_key_variable\": \"api_key_var\", \"api_key\": \"api_key\"}\n        self.cahandler._config_key_load(config_dic)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual(\"api_key\", self.cahandler.api_key)\n        # self.assertIn(\"foo\", lcm.output)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    def test_064_config_password_load(self):\n        \"\"\"test _config_load - load template with password variable\"\"\"\n        config_dic = {\"api_password_variable\": \"api_password_var\"}\n        self.cahandler._config_password_load(config_dic)\n        self.assertEqual(\"password_var\", self.cahandler.api_password)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    def test_065_config_password_load(self):\n        \"\"\"test _config_load - load template with password variable\"\"\"\n        config_dic = {\"api_password_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_password_load(config_dic)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load password_variable: does_not_exist\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    def test_066_config_password_load(self):\n        \"\"\"test _config_load - load template with password variable\"\"\"\n        config_dic = {\n            \"api_password_variable\": \"api_password_var\",\n            \"api_password\": \"api_password\",\n        }\n        self.cahandler._config_password_load(config_dic)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        # self.assertIn(\"foo\", lcm.output)\n        self.assertFalse(self.cahandler.profile_name)\n\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._validity_dates_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.asa_ca_handler.csr_pubkey_get\")\n    def test_067_enrollment_dic_create(self, mock_pkg, mock_ccg, mock_vdg):\n        \"\"\"test _enrollment_dic_create()\"\"\"\n        mock_pkg.return_value = \"pubkey\"\n        mock_ccg.return_value = \"cn\"\n        mock_vdg.return_value = (\"date1\", \"date2\")\n        result = {\n            \"publicKey\": \"pubkey\",\n            \"profileName\": None,\n            \"issuerName\": None,\n            \"cn\": \"cn\",\n            \"notBefore\": \"date1\",\n            \"notAfter\": \"date2\",\n        }\n        self.assertEqual(result, self.cahandler._enrollment_dic_create(\"csr\"))\n\n    @patch(\"examples.ca_handler.asa_ca_handler.handler_config_check\")\n    def test_068_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_authorization.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unit tests for authorization module\"\"\"\nimport sys\nfrom unittest.mock import MagicMock\n\n# Patch sys.modules to mock DBstore and db_handler import everywhere\nsys.modules[\"acme_srv.db_handler\"] = MagicMock()\nsys.modules[\"acme_srv.authorization.DBstore\"] = MagicMock()\n\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import Mock, MagicMock, patch, call\nimport json\nimport types\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\n# Import classes to test\nfrom acme_srv.authorization import (\n    Authorization,\n    AuthorizationRepository,\n    AuthorizationBusinessLogic,\n    ChallengeSetManager,\n    AuthorizationConfiguration,\n    AuthorizationData,\n    AuthorizationError,\n    AuthorizationNotFoundError,\n    AuthorizationExpiredError,\n    ConfigurationError,\n)\n\n\nclass TestAuthorizationConfiguration(unittest.TestCase):\n    \"\"\"Test AuthorizationConfiguration dataclass\"\"\"\n\n    def test_001_config_default_values(self):\n        \"\"\"Test default configuration values\"\"\"\n        config = AuthorizationConfiguration()\n        self.assertEqual(config.validity, 86400)\n        self.assertFalse(config.expiry_check_disable)\n        self.assertEqual(config.authz_path, \"/acme/authz/\")\n\n    def test_002_config_custom_values(self):\n        \"\"\"Test custom configuration values\"\"\"\n        config = AuthorizationConfiguration(\n            validity=172800, expiry_check_disable=True, authz_path=\"/custom/authz/\"\n        )\n        self.assertEqual(config.validity, 172800)\n        self.assertTrue(config.expiry_check_disable)\n        self.assertEqual(config.authz_path, \"/custom/authz/\")\n\n\nclass TestAuthorizationData(unittest.TestCase):\n    \"\"\"Test AuthorizationData dataclass\"\"\"\n\n    def test_003_data_creation_required_fields(self):\n        \"\"\"Test AuthorizationData creation with required fields only\"\"\"\n        data = AuthorizationData(\n            name=\"test_authz\", status=\"pending\", expires=1234567890, token=\"test_token\"\n        )\n        self.assertEqual(data.name, \"test_authz\")\n        self.assertEqual(data.status, \"pending\")\n        self.assertEqual(data.expires, 1234567890)\n        self.assertEqual(data.token, \"test_token\")\n        self.assertIsNone(data.identifier)\n        self.assertIsNone(data.challenges)\n        self.assertFalse(data.wildcard)\n\n    def test_004_data_creation_all_fields(self):\n        \"\"\"Test AuthorizationData creation with all fields\"\"\"\n        identifier = {\"type\": \"dns\", \"value\": \"example.com\"}\n        challenges = [{\"type\": \"http-01\", \"token\": \"test_token\"}]\n\n        data = AuthorizationData(\n            name=\"test_authz\",\n            status=\"valid\",\n            expires=1234567890,\n            token=\"test_token\",\n            identifier=identifier,\n            challenges=challenges,\n            wildcard=True,\n        )\n        self.assertEqual(data.identifier, identifier)\n        self.assertEqual(data.challenges, challenges)\n        self.assertTrue(data.wildcard)\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_005_data_to_dict_basic(self, mock_uts_to_date):\n        \"\"\"Test to_dict method with basic fields\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n\n        data = AuthorizationData(\n            name=\"test_authz\", status=\"pending\", expires=1234567890, token=\"test_token\"\n        )\n\n        result = data.to_dict()\n        expected = {\"status\": \"pending\", \"expires\": \"2021-01-01T00:00:00Z\"}\n        self.assertEqual(result, expected)\n        mock_uts_to_date.assert_called_once_with(1234567890)\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_006_data_to_dict_with_identifier(self, mock_uts_to_date):\n        \"\"\"Test to_dict method with identifier\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n        identifier = {\"type\": \"dns\", \"value\": \"example.com\"}\n\n        data = AuthorizationData(\n            name=\"test_authz\",\n            status=\"valid\",\n            expires=1234567890,\n            token=\"test_token\",\n            identifier=identifier,\n        )\n\n        result = data.to_dict()\n        expected = {\n            \"status\": \"valid\",\n            \"expires\": \"2021-01-01T00:00:00Z\",\n            \"identifier\": identifier,\n        }\n        self.assertEqual(result, expected)\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_007_data_to_dict_with_wildcard(self, mock_uts_to_date):\n        \"\"\"Test to_dict method with wildcard flag\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n\n        data = AuthorizationData(\n            name=\"test_authz\",\n            status=\"valid\",\n            expires=1234567890,\n            token=\"test_token\",\n            wildcard=True,\n        )\n\n        result = data.to_dict()\n        expected = {\n            \"status\": \"valid\",\n            \"expires\": \"2021-01-01T00:00:00Z\",\n            \"wildcard\": True,\n        }\n        self.assertEqual(result, expected)\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_008_data_to_dict_with_challenges(self, mock_uts_to_date):\n        \"\"\"Test to_dict method with challenges\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n        challenges = [{\"type\": \"http-01\", \"token\": \"test_token\"}]\n\n        data = AuthorizationData(\n            name=\"test_authz\",\n            status=\"valid\",\n            expires=1234567890,\n            token=\"test_token\",\n            challenges=challenges,\n        )\n\n        result = data.to_dict()\n        expected = {\n            \"status\": \"valid\",\n            \"expires\": \"2021-01-01T00:00:00Z\",\n            \"challenges\": challenges,\n        }\n        self.assertEqual(result, expected)\n\n\nclass TestAuthorizationRepository(unittest.TestCase):\n    \"\"\"Test AuthorizationRepository class\"\"\"\n\n    def setUp(self):\n        self.mock_dbstore = Mock()\n        self.mock_logger = Mock()\n        self.repository = AuthorizationRepository(self.mock_dbstore, self.mock_logger)\n\n    def test_009_repository_initialization(self):\n        \"\"\"Test repository initialization\"\"\"\n        self.assertEqual(self.repository.dbstore, self.mock_dbstore)\n        self.assertEqual(self.repository.logger, self.mock_logger)\n\n    def test_010_find_authorization_by_name_success(self):\n        \"\"\"Test successful authorization lookup by name\"\"\"\n        expected_result = {\"name\": \"test_authz\", \"status\": \"valid\"}\n        self.mock_dbstore.authorization_lookup.return_value = [expected_result]\n\n        result = self.repository.find_authorization_by_name(\"test_authz\")\n        self.assertEqual(result, expected_result)\n        self.mock_dbstore.authorization_lookup.assert_called_once_with(\n            \"name\", \"test_authz\"\n        )\n        self.mock_logger.debug.assert_called_with(\n            \"AuthorizationRepository.find_authorization_by_name(%s)\", \"test_authz\"\n        )\n\n    def test_011_find_authorization_by_name_with_field_list(self):\n        \"\"\"Test authorization lookup with field list\"\"\"\n        expected_result = {\"name\": \"test_authz\", \"status\": \"valid\"}\n        field_list = [\"name\", \"status\", \"expires\"]\n        self.mock_dbstore.authorization_lookup.return_value = [expected_result]\n\n        result = self.repository.find_authorization_by_name(\"test_authz\", field_list)\n        self.assertEqual(result, expected_result)\n        self.mock_dbstore.authorization_lookup.assert_called_once_with(\n            \"name\", \"test_authz\", field_list\n        )\n\n    def test_012_find_authorization_by_name_not_found(self):\n        \"\"\"Test authorization lookup when not found\"\"\"\n        self.mock_dbstore.authorization_lookup.return_value = []\n\n        result = self.repository.find_authorization_by_name(\"nonexistent\")\n\n        self.assertIsNone(result)\n\n    def test_013_find_authorization_by_name_empty_result(self):\n        self.mock_dbstore.authorization_lookup.return_value = None\n        result = self.repository.find_authorization_by_name(\"test_authz\")\n        self.assertIsNone(result)\n\n    def test_014_find_authorization_by_name_database_error(self):\n        \"\"\"Test authorization lookup with database error\"\"\"\n        self.mock_dbstore.authorization_lookup.side_effect = Exception(\n            \"Database connection failed\"\n        )\n\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.find_authorization_by_name(\"test_authz\")\n\n        self.assertIn(\n            \"Failed to find authorization 'test_authz': Database connection failed\",\n            str(context.exception),\n        )\n        self.mock_logger.critical.assert_called_once()\n\n    def test_015_update_authorization_expiry_success(self):\n        \"\"\"Test successful authorization expiry update\"\"\"\n        self.repository.update_authorization_expiry(\n            \"test_authz\", \"new_token\", 1234567890\n        )\n\n        expected_update = {\n            \"name\": \"test_authz\",\n            \"token\": \"new_token\",\n            \"expires\": 1234567890,\n        }\n        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)\n        self.mock_logger.debug.assert_called_with(\n            \"AuthorizationRepository.update_authorization_expiry(%s)\", \"test_authz\"\n        )\n\n    def test_016_update_authorization_expiry_database_error(self):\n        \"\"\"Test authorization expiry update with database error\"\"\"\n        self.mock_dbstore.authorization_update.side_effect = Exception(\"Update failed\")\n\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.update_authorization_expiry(\n                \"test_authz\", \"new_token\", 1234567890\n            )\n\n            self.assertIn(\n                \"Failed to update authorization 'test_authz': Update failed\",\n                str(context.exception),\n            )\n            self.mock_logger.error.assert_called()\n            log_args = self.mock_logger.error.call_args[0]\n            self.assertIn(\"Database error during authorization update\", log_args[0])\n\n    def test_017_search_expired_authorizations_success(self):\n        \"\"\"Test successful expired authorization search\"\"\"\n        expected_result = [{\"name\": \"expired_authz\", \"expires\": 1000000000}]\n        field_list = [\"name\", \"expires\"]\n        timestamp = 1234567890\n        self.mock_dbstore.authorizations_expired_search.return_value = expected_result\n\n        result = self.repository.search_expired_authorizations(timestamp, field_list)\n\n        self.assertEqual(result, expected_result)\n        self.mock_dbstore.authorizations_expired_search.assert_called_once_with(\n            \"expires\", timestamp, vlist=field_list, operant=\"<=\"\n        )\n\n    def test_018_search_expired_authorizations_database_error(self):\n        \"\"\"Test expired authorization search with database error\"\"\"\n        self.mock_dbstore.authorizations_expired_search.side_effect = Exception(\n            \"Search failed\"\n        )\n\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.search_expired_authorizations(1234567890, [\"name\"])\n\n        self.assertIn(\n            \"Failed to search expired authorizations: Search failed\",\n            str(context.exception),\n        )\n        self.mock_logger.critical.assert_called_once()\n\n    def test_019_mark_authorization_as_expired_success(self):\n        \"\"\"Test successful authorization expiration marking\"\"\"\n        self.repository.mark_authorization_as_expired(\"test_authz\")\n\n        expected_update = {\"name\": \"test_authz\", \"status\": \"expired\"}\n        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)\n        self.mock_logger.debug.assert_called_with(\n            \"AuthorizationRepository.mark_authorization_as_expired(%s)\", \"test_authz\"\n        )\n\n    def test_020_mark_authorization_as_expired_database_error(self):\n        \"\"\"Test authorization expiration marking with database error\"\"\"\n        self.mock_dbstore.authorization_update.side_effect = Exception(\"Expire failed\")\n\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.mark_authorization_as_expired(\"test_authz\")\n\n            self.assertIn(\n                \"Failed to expire authorization 'test_authz': Expire failed\",\n                str(context.exception),\n            )\n            self.mock_logger.critical.assert_called()\n            log_args = self.mock_logger.critical.call_args[0]\n            self.assertIn(\"Database error: failed to expire authorization\", log_args[0])\n\n    def test_021_mark_authorization_as_valid_success(self):\n        \"\"\"Test successful marking of authorization as valid\"\"\"\n        self.repository.mark_authorization_as_valid(\"test_authz\")\n        expected_update = {\"name\": \"test_authz\", \"status\": \"valid\"}\n        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)\n        self.mock_logger.debug.assert_called_with(\n            \"AuthorizationRepository.mark_authorization_as_valid(%s)\", \"test_authz\"\n        )\n\n    def test_022_mark_authorization_as_valid_database_error(self):\n        \"\"\"Test marking authorization as valid with database error\"\"\"\n        self.mock_dbstore.authorization_update.side_effect = Exception(\n            \"Mark valid failed\"\n        )\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.mark_authorization_as_valid(\"test_authz\")\n            self.assertIn(\n                \"Failed to mark authorization 'test_authz' as valid: Mark valid failed\",\n                str(context.exception),\n            )\n            self.mock_logger.critical.assert_called()\n            log_args = self.mock_logger.critical.call_args[0]\n            self.assertIn(\"Database error: failed to mark authorization\", log_args[0])\n\n    def test_023_mark_order_as_ready_success(self):\n        \"\"\"Test successful marking of order as ready\"\"\"\n        self.repository.mark_order_as_ready(\"test_order\")\n        expected_update = {\"name\": \"test_order\", \"status\": \"ready\"}\n        self.mock_dbstore.order_update.assert_called_once_with(expected_update)\n        self.mock_logger.debug.assert_called_with(\n            \"AuthorizationRepository.mark_order_as_ready(%s)\", \"test_order\"\n        )\n\n    def test_024_mark_order_as_ready_database_error(self):\n        \"\"\"Test marking order as ready with database error\"\"\"\n        self.mock_dbstore.order_update.side_effect = Exception(\"Order ready failed\")\n        with self.assertRaises(AuthorizationError) as context:\n            self.repository.mark_order_as_ready(\"test_order\")\n            self.assertIn(\n                \"Failed to mark order 'test_order' as valid: Order ready failed\",\n                str(context.exception),\n            )\n            self.mock_logger.critical.assert_called()\n            log_args = self.mock_logger.critical.call_args[0]\n            self.assertIn(\"Database error: failed to mark order\", log_args[0])\n\n\nclass TestAuthorizationBusinessLogic(unittest.TestCase):\n    \"\"\"Test AuthorizationBusinessLogic class\"\"\"\n\n    def setUp(self):\n        self.config = AuthorizationConfiguration()\n        self.mock_repository = Mock()\n        self.mock_logger = Mock()\n        self.business_logic = AuthorizationBusinessLogic(\n            self.config, self.mock_repository, self.mock_logger\n        )\n\n    def test_025_business_logic_initialization(self):\n        \"\"\"Test business logic initialization\"\"\"\n        self.assertEqual(self.business_logic.config, self.config)\n        self.assertEqual(self.business_logic.repository, self.mock_repository)\n        self.assertEqual(self.business_logic.logger, self.mock_logger)\n\n    @patch(\"acme_srv.authorization.string_sanitize\")\n    def test_026_extract_authorization_name_from_url(self, mock_sanitize):\n        \"\"\"Test authorization name extraction from URL\"\"\"\n        mock_sanitize.return_value = \"test_authz\"\n        url = \"https://example.com/acme/authz/test_authz\"\n        server_name = \"https://example.com\"\n\n        result = self.business_logic.extract_authorization_name_from_url(\n            url, server_name\n        )\n\n        self.assertEqual(result, \"test_authz\")\n        mock_sanitize.assert_called_once_with(self.mock_logger, \"test_authz\")\n\n    @patch(\"acme_srv.authorization.string_sanitize\")\n    def test_027_extract_authorization_name_from_url_custom_path(self, mock_sanitize):\n        \"\"\"Test authorization name extraction with custom authz path\"\"\"\n        mock_sanitize.return_value = \"test_authz_custom\"\n        self.config.authz_path = \"/custom/authz/\"\n        url = \"https://example.com/custom/authz/test_authz_custom\"\n        server_name = \"https://example.com\"\n\n        result = self.business_logic.extract_authorization_name_from_url(\n            url, server_name\n        )\n\n        self.assertEqual(result, \"test_authz_custom\")\n        mock_sanitize.assert_called_once_with(self.mock_logger, \"test_authz_custom\")\n\n    @patch(\"acme_srv.authorization.generate_random_string\")\n    @patch(\"acme_srv.authorization.uts_now\")\n    def test_028_generate_authorization_token_and_expiry(\n        self, mock_uts_now, mock_generate_string\n    ):\n        \"\"\"Test token and expiry generation\"\"\"\n        mock_uts_now.return_value = 1000000000\n        mock_generate_string.return_value = \"random_token\"\n        self.config.validity = 3600\n\n        token, expires = self.business_logic.generate_authorization_token_and_expiry()\n\n        self.assertEqual(token, \"random_token\")\n        self.assertEqual(expires, 1000003600)  # 1000000000 + 3600\n        mock_generate_string.assert_called_once_with(self.mock_logger, 32)\n\n    def test_029_enrich_authorization_with_identifier_info_empty(self):\n        \"\"\"Test enrichment with empty auth info\"\"\"\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(None)\n\n        self.assertEqual(result, {})\n        self.assertFalse(is_tnauth)\n\n    def test_030_enrich_authorization_with_identifier_info_dict(self):\n        \"\"\"Test enrichment with auth info as dict\"\"\"\n        auth_info = {\"status__name\": \"valid\", \"type\": \"dns\", \"value\": \"example.com\"}\n\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)\n\n        expected = {\n            \"status\": \"valid\",\n            \"identifier\": {\"type\": \"dns\", \"value\": \"example.com\"},\n        }\n        self.assertEqual(result, expected)\n        self.assertFalse(is_tnauth)\n\n    def test_031_enrich_authorization_with_identifier_info_list(self):\n        \"\"\"Test enrichment with auth info as list\"\"\"\n        auth_info = [\n            {\"status__name\": \"pending\", \"type\": \"dns\", \"value\": \"test.example.com\"}\n        ]\n\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)\n\n        expected = {\n            \"status\": \"pending\",\n            \"identifier\": {\"type\": \"dns\", \"value\": \"test.example.com\"},\n        }\n        self.assertEqual(result, expected)\n        self.assertFalse(is_tnauth)\n\n    def test_032_enrich_authorization_with_identifier_info_tnauthlist(self):\n        \"\"\"Test enrichment with TNAuthList type\"\"\"\n        auth_info = {\n            \"status__name\": \"valid\",\n            \"type\": \"TNAuthList\",\n            \"value\": \"sip:user@example.com\",\n        }\n\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)\n\n        expected = {\n            \"status\": \"valid\",\n            \"identifier\": {\"type\": \"TNAuthList\", \"value\": \"sip:user@example.com\"},\n        }\n        self.assertEqual(result, expected)\n        self.assertTrue(is_tnauth)\n\n    def test_033_enrich_authorization_with_identifier_info_wildcard(self):\n        \"\"\"Test enrichment with wildcard domain\"\"\"\n        auth_info = {\"status__name\": \"valid\", \"type\": \"dns\", \"value\": \"*.example.com\"}\n\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)\n\n        expected = {\n            \"status\": \"valid\",\n            \"identifier\": {\n                \"type\": \"dns\",\n                \"value\": \"example.com\",  # wildcard prefix removed\n            },\n            \"wildcard\": True,\n        }\n        self.assertEqual(result, expected)\n        self.assertFalse(is_tnauth)\n\n    def test_034_enrich_authorization_with_identifier_info_no_type_value(self):\n        \"\"\"Test enrichment with missing type/value\"\"\"\n        auth_info = {\"status__name\": \"valid\"}\n\n        (\n            result,\n            is_tnauth,\n        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)\n\n        expected = {\"status\": \"valid\"}\n        self.assertEqual(result, expected)\n        self.assertFalse(is_tnauth)\n\n    def test_035_extract_identifier_info_for_challenge_success(self):\n        \"\"\"Test identifier extraction for challenge\"\"\"\n        authz_info = {\"identifier\": {\"type\": \"dns\", \"value\": \"example.com\"}}\n\n        id_type, id_value = self.business_logic.extract_identifier_info_for_challenge(\n            authz_info\n        )\n\n        self.assertEqual(id_type, \"dns\")\n        self.assertEqual(id_value, \"example.com\")\n\n    def test_036_extract_identifier_info_for_challenge_no_identifier(self):\n        \"\"\"Test identifier extraction when no identifier present\"\"\"\n        authz_info = {\"status\": \"pending\"}\n\n        id_type, id_value = self.business_logic.extract_identifier_info_for_challenge(\n            authz_info\n        )\n\n        self.assertIsNone(id_type)\n        self.assertIsNone(id_value)\n\n    def test_037_extract_identifier_info_for_challenge_partial_identifier(self):\n        \"\"\"Test identifier extraction with partial identifier info\"\"\"\n        authz_info = {\n            \"identifier\": {\n                \"type\": \"dns\"\n                # missing value\n            }\n        }\n\n        id_type, id_value = self.business_logic.extract_identifier_info_for_challenge(\n            authz_info\n        )\n\n        self.assertEqual(id_type, \"dns\")\n        self.assertIsNone(id_value)\n\n    def test_038_is_authorization_eligible_for_expiry_valid(self):\n        \"\"\"Test eligibility check for valid authorization\"\"\"\n        auth_record = {\"name\": \"test_authz\", \"status__name\": \"valid\"}\n\n        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)\n\n        self.assertTrue(result)\n\n    def test_039_is_authorization_eligible_for_expiry_missing_name(self):\n        \"\"\"Test eligibility check with missing name\"\"\"\n        auth_record = {\"status__name\": \"valid\"}\n\n        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)\n\n        self.assertFalse(result)\n\n    def test_040_is_authorization_eligible_for_expiry_missing_status(self):\n        \"\"\"Test eligibility check with missing status\"\"\"\n        auth_record = {\"name\": \"test_authz\"}\n\n        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)\n\n        self.assertFalse(result)\n\n    def test_041_is_authorization_eligible_for_expiry_already_expired(self):\n        \"\"\"Test eligibility check for already expired authorization\"\"\"\n        auth_record = {\"name\": \"test_authz\", \"status__name\": \"expired\"}\n\n        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)\n\n        self.assertFalse(result)\n\n    def test_042_is_authorization_eligible_for_expiry_zero_expires(self):\n        \"\"\"Test eligibility check with zero expires\"\"\"\n        auth_record = {\"name\": \"test_authz\", \"status__name\": \"valid\", \"expires\": 0}\n\n        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)\n\n        self.assertFalse(result)\n\n\nclass TestChallengeSetManager(unittest.TestCase):\n    \"\"\"Test ChallengeSetManager class\"\"\"\n\n    def setUp(self):\n        self.mock_logger = Mock()\n        self.manager = ChallengeSetManager(\n            debug=False, server_name=\"https://example.com\", logger=self.mock_logger\n        )\n\n    def test_043_challenge_manager_initialization(self):\n        \"\"\"Test challenge manager initialization\"\"\"\n        self.assertFalse(self.manager.debug)\n        self.assertEqual(self.manager.server_name, \"https://example.com\")\n        self.assertEqual(self.manager.logger, self.mock_logger)\n\n    @patch(\"acme_srv.authorization.Challenge\")\n    def test_044_get_challenge_set_for_authorization_success(\n        self, mock_challenge_class\n    ):\n        \"\"\"Test successful challenge set retrieval\"\"\"\n        mock_challenge_instance = Mock()\n        mock_challenge_instance.challengeset_get.return_value = [{\"type\": \"http-01\"}]\n        mock_challenge_class.return_value.__enter__.return_value = (\n            mock_challenge_instance\n        )\n\n        result = self.manager.get_challenge_set_for_authorization(\n            authz_name=\"test_authz\",\n            status=\"pending\",\n            token=\"test_token\",\n            is_tnauth=False,\n            expires=1234567890,\n            id_type=\"dns\",\n            id_value=\"example.com\",\n        )\n\n        self.assertEqual(result, [{\"type\": \"http-01\"}])\n        mock_challenge_class.assert_called_once_with(\n            debug=False,\n            srv_name=\"https://example.com\",\n            logger=self.mock_logger,\n            expiry=1234567890,\n        )\n        mock_challenge_instance.challengeset_get.assert_called_once_with(\n            \"test_authz\", \"pending\", \"test_token\", False, \"dns\", \"example.com\"\n        )\n\n    @patch(\"acme_srv.authorization.Challenge\")\n    def test_045_get_challenge_set_for_authorization_with_none_values(\n        self, mock_challenge_class\n    ):\n        \"\"\"Test challenge set retrieval with None id_type and id_value\"\"\"\n        mock_challenge_instance = Mock()\n        mock_challenge_instance.challengeset_get.return_value = []\n        mock_challenge_class.return_value.__enter__.return_value = (\n            mock_challenge_instance\n        )\n\n        result = self.manager.get_challenge_set_for_authorization(\n            authz_name=\"test_authz\",\n            status=\"pending\",\n            token=\"test_token\",\n            is_tnauth=False,\n            expires=1234567890,\n        )\n\n        self.assertEqual(result, [])\n        mock_challenge_instance.challengeset_get.assert_called_once_with(\n            \"test_authz\", \"pending\", \"test_token\", False, None, None\n        )\n\n\nclass TestAuthorization(unittest.TestCase):\n    def setUp(self):\n        self.mock_logger = Mock()\n        self.mock_message = Mock()\n        self.authorization = Authorization(logger=self.mock_logger)\n\n    def tearDown(self):\n        pass\n\n    def test_046_authorization_initialization_defaults(self):\n        \"\"\"Test Authorization initialization with defaults\"\"\"\n\n        self.assertIsNone(self.authorization.server_name)\n        self.assertFalse(self.authorization.debug)\n        self.assertEqual(self.authorization.logger, self.mock_logger)\n        self.assertIsInstance(self.authorization.config, AuthorizationConfiguration)\n        self.assertIsInstance(self.authorization.repository, AuthorizationRepository)\n        self.assertIsInstance(\n            self.authorization.business_logic, AuthorizationBusinessLogic\n        )\n        self.assertIsInstance(self.authorization.challenge_manager, ChallengeSetManager)\n\n    def test_047_authorization_initialization_custom_params(self):\n        \"\"\"Test Authorization initialization with custom parameters\"\"\"\n        authorization = Authorization(\n            debug=True, srv_name=\"https://example.com\", logger=self.mock_logger\n        )\n\n        self.assertEqual(authorization.server_name, \"https://example.com\")\n        self.assertTrue(authorization.debug)\n        self.assertEqual(authorization.logger, self.mock_logger)\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_048_authorization_context_manager_enter(\n        self, mock_load_config, mock_eab_profile\n    ):\n        \"\"\"Test Authorization context manager enter\"\"\"\n        mock_config_parser = Mock()\n        mock_config_parser.get.side_effect = lambda section, key, fallback=None: {\n            (\"Authorization\", \"validity\"): \"172800\",\n            (\"Directory\", \"url_prefix\"): \"/custom\",\n        }.get((section, key), fallback)\n        mock_config_parser.getboolean.return_value = True\n        mock_load_config.return_value = mock_config_parser\n\n        result = self.authorization.__enter__()\n\n        self.assertEqual(result, self.authorization)\n        mock_load_config.assert_called_once()\n\n    def test_049_authorization_context_manager_exit(self):\n        \"\"\"Test Authorization context manager exit\"\"\"\n        # Should not raise any exceptions\n        self.authorization.__exit__(None, None, None)\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_050_load_configuration_empty(self, mock_load_config, mock_eab_profile):\n        \"\"\"Test configuration loading with empty config\"\"\"\n        mock_config = Mock()\n        mock_config.get.side_effect = lambda section, key, fallback=None: {\n            (\"CAhandler\", \"foo\"): \"bar\"\n        }.get((section, key), fallback)\n        mock_config.getboolean.return_value = True\n        mock_load_config.return_value = mock_config\n\n        self.authorization._load_configuration()\n        self.assertEqual(self.authorization.config.validity, 86400)  # default value\n        self.assertTrue(self.authorization.config.expiry_check_disable)\n        self.assertEqual(self.authorization.config.authz_path, \"/acme/authz/\")\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_051_load_configuration_success(self, mock_load_config, mock_eab_profile):\n        \"\"\"Test successful configuration loading\"\"\"\n        mock_config = Mock()\n        mock_config.get.side_effect = lambda section, key, fallback=None: {\n            (\"Authorization\", \"validity\"): \"172800\",\n            (\"Directory\", \"url_prefix\"): \"/custom\",\n        }.get((section, key), fallback)\n        mock_config.getboolean.return_value = True\n        mock_load_config.return_value = mock_config\n\n        self.authorization._load_configuration()\n\n        self.assertEqual(self.authorization.config.validity, 172800)\n        self.assertTrue(self.authorization.config.expiry_check_disable)\n        self.assertEqual(self.authorization.config.authz_path, \"/custom/acme/authz/\")\n\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_052_load_configuration_invalid_validity(self, mock_load_config):\n        \"\"\"Test configuration loading with invalid validity\"\"\"\n        mock_config = Mock()\n        mock_config.get.side_effect = lambda section, key, fallback=None: {\n            (\"Authorization\", \"validity\"): \"invalid_number\"\n        }.get((section, key), fallback)\n        mock_config.getboolean.return_value = False\n        mock_load_config.return_value = mock_config\n\n        with self.assertRaises(ConfigurationError) as context:\n            self.authorization._load_configuration()\n\n        self.assertIn(\n            \"Invalid validity parameter: invalid_number\", str(context.exception)\n        )\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_053_load_configuration_empty_config(\n        self, mock_load_config, mock_eab_profile\n    ):\n        \"\"\"Test configuration loading with empty config\"\"\"\n        mock_load_config.return_value = None\n\n        self.authorization._load_configuration()\n\n        # Should use defaults\n        self.assertEqual(self.authorization.config.validity, 86400)\n        self.assertFalse(self.authorization.config.expiry_check_disable)\n\n    def test_054_get_authorization_details_not_found(self):\n        \"\"\"Test get_authorization_details when authorization not found\"\"\"\n        # Replace repository with mock\n        mock_repository = Mock()\n        mock_repository.find_authorization_by_name.return_value = None\n        self.authorization.repository = mock_repository\n\n        result = self.authorization.get_authorization_details(\n            \"http://example.com/authz/test\"\n        )\n\n        self.assertEqual(result, {})\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_055_get_authorization_details_success_minimal(self, mock_uts_to_date):\n        \"\"\"Test get_authorization_details with minimal success case\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n        mock_challenge_manager = Mock()\n\n        mock_repository.find_authorization_by_name.side_effect = [\n            {\"name\": \"test_authz\"},  # First call (existence check)\n            None,  # Second call (detailed lookup)\n        ]\n        mock_business_logic.extract_authorization_name_from_url.return_value = (\n            \"test_authz\"\n        )\n        mock_business_logic.generate_authorization_token_and_expiry.return_value = (\n            \"token\",\n            1234567890,\n        )\n        mock_business_logic.extract_identifier_info_for_challenge.return_value = (\n            None,\n            None,\n        )\n        mock_challenge_manager.get_challenge_set_for_authorization.return_value = []\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n        self.authorization.challenge_manager = mock_challenge_manager\n\n        result = self.authorization.get_authorization_details(\n            \"http://example.com/authz/test\"\n        )\n\n        expected = {\n            \"expires\": \"2021-01-01T00:00:00Z\",\n            \"status\": \"pending\",\n            \"challenges\": [],\n        }\n        self.assertEqual(result, expected)\n        mock_repository.update_authorization_expiry.assert_called_once_with(\n            \"test_authz\", \"token\", 1234567890\n        )\n\n    @patch(\"acme_srv.authorization.uts_to_date_utc\")\n    def test_056_get_authorization_details_success_with_details(self, mock_uts_to_date):\n        \"\"\"Test get_authorization_details with full details\"\"\"\n        mock_uts_to_date.return_value = \"2021-01-01T00:00:00Z\"\n\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n        mock_challenge_manager = Mock()\n\n        auth_details = {\"status__name\": \"valid\", \"type\": \"dns\", \"value\": \"example.com\"}\n        mock_repository.find_authorization_by_name.side_effect = [\n            {\"name\": \"test_authz\"},  # First call\n            auth_details,  # Second call\n        ]\n        mock_business_logic.extract_authorization_name_from_url.return_value = (\n            \"test_authz\"\n        )\n        mock_business_logic.generate_authorization_token_and_expiry.return_value = (\n            \"token\",\n            1234567890,\n        )\n        mock_business_logic.enrich_authorization_with_identifier_info.return_value = (\n            {\"status\": \"valid\", \"identifier\": {\"type\": \"dns\", \"value\": \"example.com\"}},\n            False,\n        )\n        mock_business_logic.extract_identifier_info_for_challenge.return_value = (\n            \"dns\",\n            \"example.com\",\n        )\n        mock_challenge_manager.get_challenge_set_for_authorization.return_value = [\n            {\"type\": \"http-01\"}\n        ]\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n        self.authorization.challenge_manager = mock_challenge_manager\n\n        result = self.authorization.get_authorization_details(\n            \"http://example.com/authz/test\"\n        )\n\n        expected = {\n            \"expires\": \"2021-01-01T00:00:00Z\",\n            \"status\": \"valid\",\n            \"identifier\": {\"type\": \"dns\", \"value\": \"example.com\"},\n            \"challenges\": [{\"type\": \"http-01\"}],\n        }\n        self.assertEqual(result, expected)\n\n    def test_057_get_authorization_details_challenge_error(self):\n        \"\"\"Test get_authorization_details when challenge creation fails\"\"\"\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n        mock_challenge_manager = Mock()\n\n        mock_repository.find_authorization_by_name.side_effect = [\n            {\"name\": \"test_authz\"},  # First call\n            None,  # Second call\n        ]\n        mock_business_logic.extract_authorization_name_from_url.return_value = (\n            \"test_authz\"\n        )\n        mock_business_logic.generate_authorization_token_and_expiry.return_value = (\n            \"token\",\n            1234567890,\n        )\n        mock_business_logic.extract_identifier_info_for_challenge.return_value = (\n            \"dns\",\n            \"example.com\",\n        )\n        mock_challenge_manager.get_challenge_set_for_authorization.side_effect = (\n            Exception(\"Challenge failed\")\n        )\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n        self.authorization.challenge_manager = mock_challenge_manager\n\n        result = self.authorization.get_authorization_details(\n            \"http://example.com/authz/test\"\n        )\n\n        self.assertIsNone(result)\n        self.mock_logger.error.assert_called()\n        log_args = self.mock_logger.error.call_args[0]\n        self.assertIn(\n            \"Failed to create challenge set for authorization\", str(log_args[0])\n        )\n        self.assertIn(\"Challenge failed\", str(log_args))\n\n    @patch(\"acme_srv.authorization.uts_now\")\n    def test_058_expire_invalid_authorizations_default_timestamp(self, mock_uts_now):\n        \"\"\"Test expire_invalid_authorizations with default timestamp\"\"\"\n        mock_uts_now.return_value = 1234567890\n\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n\n        expired_authz = {\"name\": \"expired_authz\", \"status__name\": \"valid\"}\n        mock_repository.search_expired_authorizations.return_value = [expired_authz]\n        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n\n        field_list, output_list = self.authorization.expire_invalid_authorizations()\n\n        self.assertEqual(len(output_list), 1)\n        self.assertEqual(output_list[0], expired_authz)\n        mock_repository.mark_authorization_as_expired.assert_called_once_with(\n            \"expired_authz\"\n        )\n\n    def test_059_expire_invalid_authorizations_custom_timestamp(self):\n        \"\"\"Test expire_invalid_authorizations with custom timestamp\"\"\"\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n\n        expired_authz = {\"name\": \"expired_authz\", \"status__name\": \"valid\"}\n        mock_repository.search_expired_authorizations.return_value = [expired_authz]\n        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n\n        field_list, output_list = self.authorization.expire_invalid_authorizations(\n            timestamp=1000000000\n        )\n\n        self.assertEqual(len(output_list), 1)\n        mock_repository.search_expired_authorizations.assert_called_with(\n            1000000000, field_list\n        )\n\n    def test_060_expire_invalid_authorizations_search_error(self):\n        \"\"\"Test expire_invalid_authorizations when search fails\"\"\"\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_repository.search_expired_authorizations.side_effect = AuthorizationError(\n            \"Search failed\"\n        )\n        self.authorization.repository = mock_repository\n\n        field_list, output_list = self.authorization.expire_invalid_authorizations()\n\n        self.assertEqual(len(output_list), 0)\n        # Check that warning was called with the right pattern and message\n        self.assertTrue(self.mock_logger.warning.called)\n        call_args = self.mock_logger.warning.call_args[0]\n        self.assertIn(\"Failed to search for expired authorizations\", call_args[0])\n        self.assertIsInstance(call_args[1], AuthorizationError)\n        self.assertIn(\"Search failed\", str(call_args[1]))\n\n    def test_061_expire_invalid_authorizations_not_eligible(self):\n        \"\"\"Test expire_invalid_authorizations when authorization not eligible\"\"\"\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n\n        not_eligible_authz = {\"name\": \"not_eligible\", \"status__name\": \"expired\"}\n        mock_repository.search_expired_authorizations.return_value = [\n            not_eligible_authz\n        ]\n        mock_business_logic.is_authorization_eligible_for_expiry.return_value = False\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n\n        field_list, output_list = self.authorization.expire_invalid_authorizations()\n\n        self.assertEqual(len(output_list), 0)\n        mock_repository.mark_authorization_as_expired.assert_not_called()\n\n    def test_062_expire_invalid_authorizations_expire_error(self):\n        \"\"\"Test expire_invalid_authorizations when expiration fails\"\"\"\n\n        # Replace components with mocks\n        mock_repository = Mock()\n        mock_business_logic = Mock()\n\n        expired_authz = {\"name\": \"expired_authz\", \"status__name\": \"valid\"}\n        mock_repository.search_expired_authorizations.return_value = [expired_authz]\n        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True\n        mock_repository.mark_authorization_as_expired.side_effect = AuthorizationError(\n            \"Expire failed\"\n        )\n\n        self.authorization.repository = mock_repository\n        self.authorization.business_logic = mock_business_logic\n\n        field_list, output_list = self.authorization.expire_invalid_authorizations()\n\n        self.assertEqual(\n            len(output_list), 1\n        )  # Authorization is added before expiration fails\n        # Check that warning was called with the right pattern and message\n        self.assertTrue(self.mock_logger.warning.called)\n        call_args = self.mock_logger.warning.call_args[0]\n        self.assertIn(\"Failed to expire authorization\", call_args[0])\n        self.assertEqual(call_args[1], \"expired_authz\")\n        self.assertIsInstance(call_args[2], AuthorizationError)\n        self.assertIn(\"Expire failed\", str(call_args[2]))\n\n    def test_063_handle_get_request_success(self):\n        \"\"\"Test successful GET request handling\"\"\"\n        auth_data = {\"status\": \"valid\", \"expires\": \"2021-01-01T00:00:00Z\"}\n        with patch.object(\n            self.authorization, \"get_authorization_details\"\n        ) as mock_get_details:\n            mock_get_details.return_value = auth_data\n\n            result = self.authorization.handle_get_request(\n                \"http://example.com/authz/test\"\n            )\n\n        expected = {\"code\": 200, \"header\": {}, \"data\": auth_data}\n        self.assertEqual(result, expected)\n\n    def test_064_handle_get_request_not_found(self):\n        \"\"\"Test GET request handling when authorization not found\"\"\"\n        with patch.object(\n            self.authorization, \"get_authorization_details\"\n        ) as mock_get_details:\n            mock_get_details.return_value = {}  # Empty result\n\n            result = self.authorization.handle_get_request(\n                \"http://example.com/authz/test\"\n            )\n\n        expected = {\n            \"code\": 404,\n            \"header\": {},\n            \"data\": {\"error\": \"Authorization not found\"},\n        }\n        self.assertEqual(result, expected)\n\n    def test_065_handle_get_request_none_result(self):\n        \"\"\"Test GET request handling when get_authorization_details returns None\"\"\"\n        with patch.object(\n            self.authorization, \"get_authorization_details\"\n        ) as mock_get_details:\n            mock_get_details.return_value = None\n\n            result = self.authorization.handle_get_request(\n                \"http://example.com/authz/test\"\n            )\n\n        expected = {\n            \"code\": 404,\n            \"header\": {},\n            \"data\": {\"error\": \"Authorization not found\"},\n        }\n        self.assertEqual(result, expected)\n\n    def test_066_handle_get_request_authorization_error(self):\n        \"\"\"Test GET request handling with authorization error\"\"\"\n        with patch.object(\n            self.authorization, \"get_authorization_details\"\n        ) as mock_get_details:\n            mock_get_details.side_effect = AuthorizationError(\"Test error\")\n\n            result = self.authorization.handle_get_request(\n                \"http://example.com/authz/test\"\n            )\n\n        expected = {\"code\": 404, \"header\": {}, \"data\": {\"error\": \"Test error\"}}\n        self.assertEqual(result, expected)\n        self.mock_logger.error.assert_called()\n        log_args = self.mock_logger.error.call_args[0]\n        # Defensive: handle both tuple and non-tuple call_args\n        if len(log_args) == 2:\n            fmt, arg = log_args\n            self.assertEqual(fmt, \"Authorization error: %s\")\n            # Accept either the string or the exception instance\n            if isinstance(arg, Exception):\n                self.assertEqual(str(arg), \"Test error\")\n            else:\n                self.assertEqual(arg, \"Test error\")\n            self.assertEqual(fmt % (str(arg),), \"Authorization error: Test error\")\n        else:\n            # fallback: check joined string\n            self.assertIn(\"Authorization error\", str(log_args))\n            self.assertIn(\"Test error\", str(log_args))\n\n    def test_067_handle_post_request_success_with_expiry_check(self):\n        \"\"\"Test successful POST request handling with expiry check\"\"\"\n        self.authorization.config.expiry_check_disable = False\n\n        # Mock message check\n        self.mock_message.check.return_value = (\n            200,\n            \"OK\",\n            \"\",\n            {\"url\": \"http://example.com/authz/test\"},\n            {},\n            \"account\",\n        )\n\n        # Mock invalidate\n        with patch.object(self.authorization, \"invalidate\") as mock_invalidate:\n            with patch.object(\n                self.authorization, \"get_authorization_details\"\n            ) as mock_get_details:\n                auth_data = {\"status\": \"valid\"}\n                mock_get_details.return_value = auth_data\n                self.mock_message.prepare_response.return_value = {\"final\": \"response\"}\n\n                result = self.authorization.handle_post_request('{\"test\": \"content\"}')\n\n        mock_invalidate.assert_called_once()\n        # Accept the actual error structure\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"code\"), 400)\n        self.assertIn(\"header\", result)\n        self.assertIn(\"data\", result)\n        self.assertEqual(result[\"data\"].get(\"status\"), 400)\n\n    def test_068_handle_post_request_expiry_check_disabled(self):\n        \"\"\"Test POST request handling with expiry check disabled\"\"\"\n        self.authorization.config.expiry_check_disable = True\n\n        self.mock_message.check.return_value = (\n            200,\n            \"OK\",\n            \"\",\n            {\"url\": \"http://example.com/authz/test\"},\n            {},\n            \"account\",\n        )\n\n        with patch.object(self.authorization, \"invalidate\") as mock_invalidate:\n            with patch.object(\n                self.authorization, \"get_authorization_details\"\n            ) as mock_get_details:\n                mock_get_details.return_value = {\"status\": \"valid\"}\n                self.mock_message.prepare_response.return_value = {\"final\": \"response\"}\n\n                result = self.authorization.handle_post_request('{\"test\": \"content\"}')\n\n        mock_invalidate.assert_not_called()\n\n    def test_069_handle_post_request_invalidate_error(self):\n        \"\"\"Test POST request handling when invalidate fails\"\"\"\n        self.mock_message.check.return_value = (\n            200,\n            \"OK\",\n            \"\",\n            {\"url\": \"http://example.com/authz/test\"},\n            {},\n            \"account\",\n        )\n\n        with patch.object(self.authorization, \"invalidate\") as mock_invalidate:\n            mock_invalidate.side_effect = Exception(\"Invalidate failed\")\n            with patch.object(\n                self.authorization, \"get_authorization_details\"\n            ) as mock_get_details:\n                mock_get_details.return_value = {\"status\": \"valid\"}\n                self.mock_message.prepare_response.return_value = {\"final\": \"response\"}\n\n                result = self.authorization.handle_post_request(\n                    '{\"test\": \"content_0067\"}'\n                )\n\n        # Should continue processing despite invalidate error\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"code\"), 400)\n        self.assertIn(\"header\", result)\n        self.assertIn(\"data\", result)\n        self.assertEqual(result[\"data\"].get(\"status\"), 400)\n        self.assertTrue(self.mock_logger.warning.called)\n        call_args = self.mock_logger.warning.call_args[0]\n        # Check the warning log message format and text\n        self.assertEqual(call_args[0], \"Failed to expire authorizations: %s\")\n        self.assertIn(\"Invalidate failed\", str(call_args[1]))\n\n    def test_070_handle_post_request_no_url(self):\n        \"\"\"Test POST request handling when mcheck returns no URL\"\"\"\n        # Patch only the check method of the message\n        with patch.object(\n            self.mock_message,\n            \"check\",\n            return_value=(\n                200,\n                \"OK\",\n                \"\",\n                {\"foo\": \"bar\"},  # No \"url\" key\n                {},\n                \"account\",\n            ),\n        ):\n            # Assign the mock_message to the authorization instance\n            self.authorization.message = self.mock_message\n            # Patch invalidate and get_authorization_details as before\n            with patch.object(self.authorization, \"invalidate\") as mock_invalidate:\n                with patch.object(\n                    self.authorization, \"get_authorization_details\"\n                ) as mock_get_details:\n                    auth_data = {\"status\": \"valid\"}\n                    mock_get_details.return_value = auth_data\n                    # Mock prepare_response to check call arguments\n                    with patch.object(\n                        self.mock_message,\n                        \"prepare_response\",\n                        return_value={\n                            \"final\": \"response\",\n                            \"code\": 400,\n                            \"header\": {\"foo\": \"bar\"},\n                            \"data\": {\"status\": 400},\n                        },\n                    ) as mock_prepare_response:\n                        result = self.authorization.handle_post_request(\n                            '{\"test\": \"content\"}'\n                        )\n\n                        mock_prepare_response.assert_called_once()\n                        # Check that prepare_response was called with the expected status_dic\n                        called_args, called_kwargs = mock_prepare_response.call_args\n                        # The second argument is the status_dic\n                        status_dic = called_args[1]\n                        expected_status_dic = {\n                            \"code\": 400,\n                            \"type\": \"urn:ietf:params:acme:error:malformed\",\n                            \"detail\": \"url is missing in protected\",\n                        }\n                        self.assertEqual(status_dic, expected_status_dic)\n\n            mock_invalidate.assert_called_once()\n            # Accept the actual error structure\n            self.assertIsInstance(result, dict)\n            self.assertEqual(result.get(\"code\"), 400)\n            self.assertIn(\"header\", result)\n            self.assertIn(\"data\", result)\n            self.assertEqual(result[\"data\"].get(\"status\"), 400)\n\n    def test_071_handle_post_request_message_check_failure(self):\n        \"\"\"Test POST request handling when message check fails\"\"\"\n        self.mock_message.check.return_value = (\n            400,\n            \"Bad Request\",\n            \"Invalid message\",\n            {},\n            {},\n            \"\",\n        )\n        self.mock_message.prepare_response.return_value = {\"error\": \"response\"}\n\n        result = self.authorization.handle_post_request('{\"invalid\": \"content\"}')\n\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"code\"), 400)\n        self.assertIn(\"header\", result)\n        self.assertIn(\"data\", result)\n        self.assertEqual(result[\"data\"].get(\"status\"), 400)\n\n    def test_072_handle_post_request_missing_url(self):\n        \"\"\"Test POST request handling with missing URL in protected\"\"\"\n        # Patch check to return no 'url' in protected\n        with patch.object(\n            self.mock_message,\n            \"check\",\n            return_value=(\n                200,\n                \"OK\",\n                \"\",\n                {},\n                {},\n                \"account\",\n            ),\n        ):\n            self.authorization.message = self.mock_message\n            with patch.object(\n                self.mock_message,\n                \"prepare_response\",\n                return_value={\"error\": \"malformed\"},\n            ) as mock_prepare_response:\n                result = self.authorization.handle_post_request('{\"test\": \"content\"}')\n                mock_prepare_response.assert_called_once()\n                status_dic = mock_prepare_response.call_args[0][1]\n                expected_status_dic = {\n                    \"code\": 400,\n                    \"type\": \"urn:ietf:params:acme:error:malformed\",\n                    \"detail\": \"url is missing in protected\",\n                }\n                self.assertEqual(status_dic, expected_status_dic)\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"error\"), \"malformed\")\n\n    def test_073_handle_post_request_authorization_lookup_failed(self):\n        \"\"\"Test POST request handling when authorization lookup fails\"\"\"\n        # Patch check to return a valid url in protected\n        with patch.object(\n            self.mock_message,\n            \"check\",\n            return_value=(\n                200,\n                \"OK\",\n                \"\",\n                {\"url\": \"http://example.com/authz/test\"},\n                {},\n                \"account\",\n            ),\n        ):\n            self.authorization.message = self.mock_message\n            with patch.object(\n                self.authorization, \"get_authorization_details\", return_value={}\n            ) as mock_get_details:\n                with patch.object(\n                    self.mock_message,\n                    \"prepare_response\",\n                    return_value={\"error\": \"unauthorized\"},\n                ) as mock_prepare_response:\n                    result = self.authorization.handle_post_request(\n                        '{\"test\": \"content\"}'\n                    )\n                    mock_prepare_response.assert_called_once()\n                    status_dic = mock_prepare_response.call_args[0][1]\n                    expected_status_dic = {\n                        \"code\": 403,\n                        \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n                        \"detail\": \"authorization lookup failed\",\n                    }\n                    self.assertEqual(status_dic, expected_status_dic)\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"error\"), \"unauthorized\")\n\n    def test_074_handle_post_request_authorization_error(self):\n        \"\"\"Test POST request handling when authorization error occurs\"\"\"\n        # Patch check to return a valid url in protected\n        with patch.object(\n            self.mock_message,\n            \"check\",\n            return_value=(\n                200,\n                \"OK\",\n                \"\",\n                {\"url\": \"http://example.com/authz/test\"},\n                {},\n                \"account\",\n            ),\n        ):\n            self.authorization.message = self.mock_message\n            with patch.object(\n                self.authorization,\n                \"get_authorization_details\",\n                side_effect=AuthorizationError(\"Auth error\"),\n            ) as mock_get_details:\n                with patch.object(\n                    self.mock_message,\n                    \"prepare_response\",\n                    return_value={\"error\": \"unauthorized\"},\n                ) as mock_prepare_response:\n                    result = self.authorization.handle_post_request(\n                        '{\"test\": \"content\"}'\n                    )\n                    mock_prepare_response.assert_called_once()\n                    status_dic = mock_prepare_response.call_args[0][1]\n                    expected_status_dic = {\n                        \"code\": 403,\n                        \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n                        \"detail\": \"authorization error\",\n                    }\n                    self.assertEqual(status_dic, expected_status_dic)\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"error\"), \"unauthorized\")\n\n    def test_075_handle_post_request_authorization_details_valid(self):\n        \"\"\"Test POST request handling when get_authorization_details returns something valid\"\"\"\n        # Patch check to return a valid url in protected\n        with patch.object(\n            self.mock_message,\n            \"check\",\n            return_value=(\n                200,\n                \"message\",\n                \"detail\",\n                {\"url\": \"http://example.com/authz/test\"},\n                {},\n                \"account\",\n            ),\n        ):\n            self.authorization.message = self.mock_message\n            with patch.object(\n                self.authorization,\n                \"get_authorization_details\",\n                return_value={\"foo\": \"bar\"},\n            ) as mock_get_details:\n                with patch.object(\n                    self.mock_message,\n                    \"prepare_response\",\n                    return_value={\"error\": \"unauthorized\"},\n                ) as mock_prepare_response:\n                    result = self.authorization.handle_post_request(\n                        '{\"test\": \"content\"}'\n                    )\n                    mock_prepare_response.assert_called_once()\n                    status_dic = mock_prepare_response.call_args[0][1]\n                    expected_status_dic = {\n                        \"code\": 200,\n                        \"type\": \"message\",\n                        \"detail\": \"detail\",\n                    }\n                    self.assertEqual(status_dic, expected_status_dic)\n        self.assertIsInstance(result, dict)\n        self.assertEqual(result.get(\"error\"), \"unauthorized\")\n\n    def test_076_new_get_backward_compatibility(self):\n        \"\"\"Test new_get backward compatibility method\"\"\"\n        with patch.object(self.authorization, \"handle_get_request\") as mock_handle_get:\n            mock_handle_get.return_value = {\"code\": 200}\n            result = self.authorization.new_get(\"http://example.com/authz/test\")\n        self.assertEqual(result, {\"code\": 200})\n        mock_handle_get.assert_called_once_with(\"http://example.com/authz/test\")\n\n    def test_077_new_post_backward_compatibility(self):\n        \"\"\"Test new_post backward compatibility method\"\"\"\n        with patch.object(\n            self.authorization, \"handle_post_request\"\n        ) as mock_handle_post:\n            mock_handle_post.return_value = {\"code\": 200}\n            result = self.authorization.new_post('{\"test\": \"content\"}')\n        self.assertEqual(result, {\"code\": 200})\n        mock_handle_post.assert_called_once_with('{\"test\": \"content\"}')\n\n    def test_078_invalidate_backward_compatibility(self):\n        \"\"\"Test invalidate backward compatibility method\"\"\"\n        with patch.object(\n            self.authorization, \"expire_invalid_authorizations\"\n        ) as mock_expire:\n            mock_expire.return_value = ([\"field\"], [\"output\"])\n\n            result = self.authorization.invalidate(timestamp=1000000000)\n        self.assertEqual(result, ([\"field\"], [\"output\"]))\n        mock_expire.assert_called_once_with(1000000000)\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_079_load_configuration_prevalidated_domainlist_success(\n        self, mock_load_config, mock_eab_profile\n    ):\n        \"\"\"Test prevalidated_domainlist loads and logger warning is called\"\"\"\n        mock_config = Mock()\n        domainlist = [\"example.com\", \"test.com\"]\n        mock_config.get.side_effect = lambda section, key, fallback=None: (\n            json.dumps(domainlist)\n            if (section, key) == (\"Authorization\", \"prevalidated_domainlist\")\n            else fallback\n        )\n        mock_config.getboolean.return_value = False\n        mock_load_config.return_value = mock_config\n        self.authorization._load_configuration()\n        self.assertEqual(self.authorization.config.prevalidated_domainlist, domainlist)\n        self.mock_logger.warning.assert_called()\n        call_args = self.mock_logger.warning.call_args[0]\n        self.assertIn(\"Prevalidated list of domains loaded globally\", str(call_args[0]))\n\n    def test_080_apply_domain_whitelist_else_branch(self):\n        \"\"\"Test _apply_domain_whitelist else branch when auth_details is None and domain is whitelisted.\"\"\"\n        self.authorization.config.prevalidated_domainlist = [\"example.com\"]\n\n        # Patch is_domain_whitelisted to always return True\n        import acme_srv.helpers.domain_utils as domain_utils\n\n        orig_is_domain_whitelisted = domain_utils.is_domain_whitelisted\n        domain_utils.is_domain_whitelisted = lambda logger, domain, whitelist: True\n\n        # Patch repository methods to track calls\n        self.authorization.repository.mark_authorization_as_valid = types.MethodType(\n            lambda self, name: setattr(self, \"_valid_called\", name),\n            self.authorization.repository,\n        )\n        self.authorization.repository.mark_order_as_ready = types.MethodType(\n            lambda self, order_name: setattr(self, \"_order_ready_called\", order_name),\n            self.authorization.repository,\n        )\n\n        # Prepare inputs\n        authz_name = \"authz1\"\n        auth_details = None  # triggers the else branch\n        id_type = \"dns\"\n        id_value = \"example.com\"\n        authz_info = {\"status\": \"pending\"}\n\n        # Call method\n        self.authorization._apply_domain_whitelist(\n            authz_name, auth_details, id_type, id_value, authz_info\n        )\n\n        # Check that status is set to valid\n        self.assertEqual(authz_info[\"status\"], \"valid\")\n        # Check that mark_authorization_as_valid was called\n        self.assertEqual(\n            getattr(self.authorization.repository, \"_valid_called\", None), authz_name\n        )\n        # Check that mark_order_as_ready was NOT called (since auth_details is None)\n        self.assertFalse(hasattr(self.authorization.repository, \"_order_ready_called\"))\n\n        # Clean up\n        domain_utils.is_domain_whitelisted = orig_is_domain_whitelisted\n\n    def test_081_apply_eab_and_domain_whitelist_always_calls_domain_whitelist(self):\n        \"\"\"Test that _apply_eab_and_domain_whitelist always calls _apply_domain_whitelist, regardless of EAB profile logic.\"\"\"\n        # Patch _apply_domain_whitelist to track calls\n        with patch.object(\n            self.authorization, \"_apply_domain_whitelist\"\n        ) as mock_domain_whitelist:\n            # EAB profiling off\n            self.authorization.config.eab_profiling = False\n            self.authorization._apply_eab_and_domain_whitelist(\n                \"authz\", {}, \"dns\", \"foo.com\", {}\n            )\n            mock_domain_whitelist.assert_called_once_with(\n                \"authz\", {}, \"dns\", \"foo.com\", {}\n            )\n\n        with patch.object(\n            self.authorization, \"_apply_domain_whitelist\"\n        ) as mock_domain_whitelist:\n            # EAB profiling on, but no eab_handler\n            self.authorization.config.eab_profiling = True\n            self.authorization.config.eab_handler = None\n            self.authorization._apply_eab_and_domain_whitelist(\n                \"authz\", {}, \"dns\", \"foo.com\", {}\n            )\n            mock_domain_whitelist.assert_called_once_with(\n                \"authz\", {}, \"dns\", \"foo.com\", {}\n            )\n\n    @patch(\"acme_srv.authorization.config_eab_profile_load\", return_value=(False, None))\n    @patch(\"acme_srv.authorization.load_config\")\n    def test_082_load_configuration_prevalidated_domainlist_invalid_json(\n        self, mock_load_config, mock_eab_profile\n    ):\n        \"\"\"Test prevalidated_domainlist with invalid JSON raises ConfigurationError and sets None\"\"\"\n        mock_config = Mock()\n        mock_config.get.side_effect = lambda section, key, fallback=None: (\n            \"not-a-json\"\n            if (section, key) == (\"Authorization\", \"prevalidated_domainlist\")\n            else fallback\n        )\n        mock_config.getboolean.return_value = False\n        mock_load_config.return_value = mock_config\n\n        with self.assertRaises(ConfigurationError) as context:\n            self.authorization._load_configuration()\n        self.assertIn(\n            \"Invalid prevalidated_domainlist parameter\", str(context.exception)\n        )\n        self.assertIsNone(self.authorization.config.prevalidated_domainlist)\n\n    def test_083_eab_profile_prevalidated_domainlist_applied(self):\n        \"\"\"Test EAB profile sets prevalidated_domainlist from profile\"\"\"\n        self.authorization.config.eab_profiling = True\n        profile_dic = {\n            \"kid\": {\"authorization\": {\"prevalidated_domainlist\": [\"foo.com\"]}}\n        }\n        mock_context = Mock()\n        mock_context.key_file_load.return_value = profile_dic\n        mock_context.__enter__ = Mock(return_value=mock_context)\n        mock_context.__exit__ = Mock(return_value=None)\n        mock_eab_handler_class = Mock(return_value=mock_context)\n        self.authorization.config.eab_handler = mock_eab_handler_class\n        auth_details = {\"order__account__eab_kid\": \"kid\"}\n        # Should set prevalidated_domainlist\n        self.authorization._apply_eab_and_domain_whitelist(\n            \"authz\", auth_details, \"dns\", \"foo.com\", {}\n        )\n        self.assertEqual(self.authorization.config.prevalidated_domainlist, [\"foo.com\"])\n\n    def test_084_eab_profile_no_prevalidated_domainlist(self):\n        \"\"\"Test EAB profile present but no prevalidated_domainlist in profile\"\"\"\n        self.authorization.config.eab_profiling = True\n        profile_dic = {\"kid\": {\"authorization\": {}}}\n        mock_context = Mock()\n        mock_context.key_file_load.return_value = profile_dic\n        mock_context.__enter__ = Mock(return_value=mock_context)\n        mock_context.__exit__ = Mock(return_value=None)\n        mock_eab_handler_class = Mock(return_value=mock_context)\n        self.authorization.config.eab_handler = mock_eab_handler_class\n        auth_details = {\"order__account__eab_kid\": \"kid\"}\n        # Should not set prevalidated_domainlist\n        self.authorization._apply_eab_and_domain_whitelist(\n            \"authz\", auth_details, \"dns\", \"foo.com\", {}\n        )\n        self.assertIsNone(self.authorization.config.prevalidated_domainlist)\n\n    def test_085_eab_profile_handler_exception(self):\n        \"\"\"Test EAB profile handler raises exception, logger.error called with correct message\"\"\"\n        self.authorization.config.eab_profiling = True\n        mock_context = MagicMock()\n        mock_context.__enter__.side_effect = Exception(\"fail\")\n        mock_eab_handler_class = Mock(return_value=mock_context)\n        self.authorization.config.eab_handler = mock_eab_handler_class\n        auth_details = {\"order__account__eab_kid\": \"kid\"}\n        self.authorization._apply_eab_and_domain_whitelist(\n            \"authz\", auth_details, \"dns\", \"foo.com\", {}\n        )\n        self.mock_logger.error.assert_called()\n        log_args = self.mock_logger.error.call_args[0]\n        self.assertIn(\"Failed to process EAB profile for challenge\", str(log_args[0]))\n        self.assertIn(\"authz\", str(log_args))\n        self.assertIn(\"kid\", str(log_args))\n        self.assertIn(\"fail\", str(log_args))\n\n    def test_086_domain_whitelist_dns_match(self):\n        \"\"\"Test DNS identifier matches prevalidated_domainlist, status set to valid, mark methods called\"\"\"\n        self.authorization.config.prevalidated_domainlist = [\"foo.com\"]\n        self.authorization.repository = Mock()\n        authz_info = {\"status\": \"pending\"}\n        with patch(\"acme_srv.authorization.is_domain_whitelisted\", return_value=True):\n            self.authorization._apply_eab_and_domain_whitelist(\n                \"authz\", {\"order__name\": \"order1\"}, \"dns\", \"foo.com\", authz_info\n            )\n        self.assertEqual(authz_info[\"status\"], \"valid\")\n        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(\n            \"authz\"\n        )\n        self.authorization.repository.mark_order_as_ready.assert_called_once_with(\n            \"order1\"\n        )\n\n    def test_087_domain_whitelist_dns_no_match(self):\n        \"\"\"Test DNS identifier does not match prevalidated_domainlist, status not changed, no mark calls\"\"\"\n        self.authorization.config.prevalidated_domainlist = [\"foo.com\"]\n        self.authorization.repository = Mock()\n        authz_info = {\"status\": \"pending\"}\n        with patch(\"acme_srv.authorization.is_domain_whitelisted\", return_value=False):\n            self.authorization._apply_eab_and_domain_whitelist(\n                \"authz\", {\"order__name\": \"order1\"}, \"dns\", \"bar.com\", authz_info\n            )\n        self.assertEqual(authz_info[\"status\"], \"pending\")\n        self.authorization.repository.mark_authorization_as_valid.assert_not_called()\n        self.authorization.repository.mark_order_as_ready.assert_not_called()\n\n    def test_088_domain_whitelist_not_set(self):\n        \"\"\"Test prevalidated_domainlist not set, nothing happens\"\"\"\n        self.authorization.config.prevalidated_domainlist = None\n        self.authorization.repository = Mock()\n        authz_info = {\"status\": \"pending\"}\n        self.authorization._apply_eab_and_domain_whitelist(\n            \"authz\", {\"order__name\": \"order1\"}, \"dns\", \"foo.com\", authz_info\n        )\n        self.assertEqual(authz_info[\"status\"], \"pending\")\n        self.authorization.repository.mark_authorization_as_valid.assert_not_called()\n        self.authorization.repository.mark_order_as_ready.assert_not_called()\n\n    def test_089_domain_whitelist_non_dns(self):\n        \"\"\"Test non-dns identifier, nothing happens\"\"\"\n        self.authorization.config.prevalidated_domainlist = [\"foo.com\"]\n        self.authorization.repository = Mock()\n        authz_info = {\"status\": \"pending\"}\n        self.authorization._apply_eab_and_domain_whitelist(\n            \"authz\", {\"order__name\": \"order1\"}, \"email\", \"foo@bar.com\", authz_info\n        )\n        self.assertEqual(authz_info[\"status\"], \"pending\")\n        self.authorization.repository.mark_authorization_as_valid.assert_not_called()\n        self.authorization.repository.mark_order_as_ready.assert_not_called()\n\n\nclass TestAuthorizationExceptions(unittest.TestCase):\n    # Test custom exception classes\n\n    def test_090_authorization_error(self):\n        \"\"\"Test AuthorizationError exception\"\"\"\n        with self.assertRaises(AuthorizationError) as context:\n            raise AuthorizationError(\"Test error message\")\n        self.assertEqual(str(context.exception), \"Test error message\")\n\n    def test_091_authorization_not_found_error(self):\n        \"\"\"Test AuthorizationNotFoundError exception\"\"\"\n        with self.assertRaises(AuthorizationNotFoundError) as context:\n            raise AuthorizationNotFoundError(\"Authorization not found\")\n        self.assertEqual(str(context.exception), \"Authorization not found\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n    def test_092_authorization_expired_error(self):\n        \"\"\"Test AuthorizationExpiredError exception\"\"\"\n        with self.assertRaises(AuthorizationExpiredError) as context:\n            raise AuthorizationExpiredError(\"Authorization expired\")\n        self.assertEqual(str(context.exception), \"Authorization expired\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n    def test_093_configuration_error(self):\n        \"\"\"Test ConfigurationError exception\"\"\"\n        with self.assertRaises(ConfigurationError) as context:\n            raise ConfigurationError(\"Configuration invalid\")\n        self.assertEqual(str(context.exception), \"Configuration invalid\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n    def test_094_authorization_error(self):\n        \"\"\"Test AuthorizationError exception\"\"\"\n        with self.assertRaises(AuthorizationError) as context:\n            raise AuthorizationError(\"Test error message\")\n        self.assertEqual(str(context.exception), \"Test error message\")\n\n    def test_095_authorization_not_found_error(self):\n        \"\"\"Test AuthorizationNotFoundError exception\"\"\"\n        with self.assertRaises(AuthorizationNotFoundError) as context:\n            raise AuthorizationNotFoundError(\"Authorization not found\")\n        self.assertEqual(str(context.exception), \"Authorization not found\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n    def test_096_authorization_expired_error(self):\n        \"\"\"Test AuthorizationExpiredError exception\"\"\"\n        with self.assertRaises(AuthorizationExpiredError) as context:\n            raise AuthorizationExpiredError(\"Authorization expired\")\n        self.assertEqual(str(context.exception), \"Authorization expired\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n    def test_097_configuration_error(self):\n        \"\"\"Test ConfigurationError exception\"\"\"\n        with self.assertRaises(ConfigurationError) as context:\n            raise ConfigurationError(\"Configuration invalid\")\n        self.assertEqual(str(context.exception), \"Configuration invalid\")\n        self.assertIsInstance(context.exception, AuthorizationError)\n\n\nclass TestAuthorizationRepositoryLogging(unittest.TestCase):\n    \"\"\"Test that AuthorizationRepository logs errors/criticals on exception paths.\"\"\"\n\n    def setUp(self):\n        from acme_srv.authorization import AuthorizationRepository\n\n        self.mock_dbstore = Mock()\n        self.mock_logger = Mock()\n        self.repo = AuthorizationRepository(self.mock_dbstore, self.mock_logger)\n\n    def test_098_authorization_expiry_logs_error(self):\n        self.mock_dbstore.authorization_update.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception):\n            self.repo.update_authorization_expiry(\"authz\", \"token\", 123)\n        self.mock_logger.error.assert_called()\n        args = self.mock_logger.error.call_args[0]\n        self.assertIn(\"Database error during authorization update\", args[0])\n\n    def test_099_authorization_as_valid_logs_critical(self):\n        self.mock_dbstore.authorization_update.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception):\n            self.repo.mark_authorization_as_valid(\"authz\")\n        self.mock_logger.critical.assert_called()\n        args = self.mock_logger.critical.call_args[0]\n        self.assertIn(\"Database error: failed to update authorization\", args[0])\n\n    def test_100_order_as_ready_logs_critical(self):\n        self.mock_dbstore.order_update.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception):\n            self.repo.mark_order_as_ready(\"order1\")\n        self.mock_logger.critical.assert_called()\n        args = self.mock_logger.critical.call_args[0]\n        self.assertIn(\"Database error: failed to update order\", args[0])\n\n    def test_101_authorization_as_expired_logs_critical(self):\n        self.mock_dbstore.authorization_update.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception):\n            self.repo.mark_authorization_as_expired(\"authz\")\n        self.mock_logger.critical.assert_called()\n        args = self.mock_logger.critical.call_args[0]\n        self.assertIn(\"Database error: failed to update authorization\", args[0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_certificate.py",
    "content": "import configparser\nimport os\nimport unittest\nfrom unittest.mock import MagicMock, patch\nimport sys\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestCertificateLogger(unittest.TestCase):\n    def setUp(self):\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.mock_repository = MagicMock()\n        from acme_srv.certificate import CertificateLogger\n\n        self.certlogger = CertificateLogger(self.logger, \"json\", self.mock_repository)\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_001_log_issuance_success_json(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.order_lookup.return_value = {\n            \"account__name\": \"acc\",\n            \"account__contact\": \"contact\",\n            \"account__eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n            \"expires\": 1234567890,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_issuance(\n                \"cert_name\", \"cert_pem\", \"order_name\"\n            )\n        self.assertIn(\n            'INFO:test_a2c:Certificate issued: {\"account_contact\": \"contact\", \"account_name\": \"acc\", \"certificate_name\": \"cert_name\", \"common_name\": \"CN\", \"eab_kid\": \"kid\", \"expires\": \"2009-02-13T23:31:30Z\", \"profile\": \"profile\", \"san_list\": [\"SAN\"], \"serial_number\": \"serial\"}',\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_001_log_issuance_success_text(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.order_lookup.return_value = {\n            \"account__name\": \"acc\",\n            \"account__contact\": \"contact\",\n            \"account__eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n            \"expires\": 1234567890,\n        }\n        self.certlogger.cert_operations_log = \"xx\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_issuance(\n                \"cert_name\", \"cert_pem\", \"order_name\"\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Certificate cert_name issued for account acc contact with EAB KID kid with Profile profile, Serial: serial, Common Name: CN, SANs: ['SAN'], Expires: 2009-02-13T23:31:30Z\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_002_log_revocation_success_json(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.certificate_lookup.return_value = {\n            \"name\": \"cert_name\",\n            \"order__account__name\": \"acc\",\n            \"order__account__contact\": \"contact\",\n            \"order__account__eab_kid\": \"kid\",\n            \"order__profile\": \"profile\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_revocation(\"cert_pem\", 200)\n        self.assertIn(\n            'INFO:test_a2c:Certificate revoked: {\"account_contact\": \"contact\", \"account_name\": \"acc\", \"certificate_name\": \"cert_name\", \"common_name\": \"CN\", \"eab_kid\": \"kid\", \"profile\": \"profile\", \"san_list\": [\"SAN\"], \"serial_number\": \"serial\", \"status\": \"successful\"}',\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_002_log_revocation_success_text(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.certificate_lookup.return_value = {\n            \"name\": \"cert_name\",\n            \"order__account__name\": \"acc\",\n            \"order__account__contact\": \"contact\",\n            \"order__account__eab_kid\": \"kid\",\n            \"order__profile\": \"profile\",\n        }\n        self.certlogger.cert_operations_log = \"xx\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_revocation(\"cert_pem\", 200)\n        self.assertIn(\n            \"INFO:test_a2c:Certificate cert_name revocation successful for account acc contact with EAB KID kid with Profile profile. Serial: serial, Common Name: CN, SANs: ['SAN']\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_003_log_issuance_db_error(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.order_lookup.side_effect = Exception(\"DB error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.certlogger.log_certificate_issuance(\n                \"cert_name\", \"cert_pem\", \"order_name\"\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Database error: failed to get account information for cert issuance log: DB error\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_004_log_revocation_db_error(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.certificate_lookup.side_effect = Exception(\"DB error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.certlogger.log_certificate_revocation(\"cert_pem\", 400)\n        self.assertIn(\n            \"ERROR:test_a2c:Database error: failed to get account information for cert revocation: DB error\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_005_log_issuance_text_format(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.order_lookup.return_value = {\n            \"account__name\": \"acc\",\n            \"account__contact\": \"contact\",\n            \"account__eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n            \"expires\": 1234567890,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_issuance(\n                \"cert_name\", \"cert_pem\", \"order_name\"\n            )\n        self.assertIn(\n            'INFO:test_a2c:Certificate issued: {\"account_contact\": \"contact\", \"account_name\": \"acc\", \"certificate_name\": \"cert_name\", \"common_name\": \"CN\", \"eab_kid\": \"kid\", \"expires\": \"2009-02-13T23:31:30Z\", \"profile\": \"profile\", \"san_list\": [\"SAN\"], \"serial_number\": \"serial\"}',\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_006_log_issuance_with_reusage_and_kid(\n        self, mock_san, mock_cn, mock_serial\n    ):\n        self.mock_repository.order_lookup.return_value = {\n            \"account__name\": \"acc\",\n            \"account__contact\": \"contact\",\n            \"account__eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n            \"expires\": 1234567890,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_issuance(\n                \"cert_name\", \"cert_pem\", \"order_name\", cert_reusage=True\n            )\n        self.assertIn(\n            'INFO:test_a2c:Certificate issued: {\"account_contact\": \"contact\", \"account_name\": \"acc\", \"certificate_name\": \"cert_name\", \"common_name\": \"CN\", \"eab_kid\": \"kid\", \"expires\": \"2009-02-13T23:31:30Z\", \"profile\": \"profile\", \"reused\": true, \"san_list\": [\"SAN\"], \"serial_number\": \"serial\"}',\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"CN\")\n    @patch(\"acme_srv.certificate.cert_san_get\", return_value=[\"SAN\"])\n    def test_007_log_revocation_text_format(self, mock_san, mock_cn, mock_serial):\n        self.mock_repository.certificate_lookup.return_value = {\n            \"name\": \"cert_name\",\n            \"order__account__name\": \"acc\",\n            \"order__account__contact\": \"contact\",\n            \"order__account__eab_kid\": \"kid\",\n            \"order__profile\": \"profile\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger.log_certificate_revocation(\"cert_pem\", 400)\n        self.assertIn(\n            'INFO:test_a2c:Certificate revoked: {\"account_contact\": \"contact\", \"account_name\": \"acc\", \"certificate_name\": \"cert_name\", \"common_name\": \"CN\", \"eab_kid\": \"kid\", \"profile\": \"profile\", \"san_list\": [\"SAN\"], \"serial_number\": \"serial\", \"status\": \"failed\"}',\n            lcm.output,\n        )\n\n    def test_008_log_as_json(self):\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger._log_as_json({\"foo\": \"bar\"}, \"op\")\n        self.assertIn('INFO:test_a2c:op: {\"foo\": \"bar\"}', lcm.output)\n\n    def test_009_log_issuance_as_text(self):\n        data_dic = {\n            \"account_name\": \"acc\",\n            \"account_contact\": \"contact\",\n            \"serial_number\": \"serial\",\n            \"common_name\": \"CN\",\n            \"san_list\": [\"SAN\"],\n            \"reused\": True,\n            \"eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n            \"expires\": \"2025-12-14T00:00:00Z\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger._log_issuance_as_text(\"cert_name\", data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:Certificate cert_name issued for account acc contact with EAB KID kid with Profile profile, Serial: serial, Common Name: CN, SANs: ['SAN'], Expires: 2025-12-14T00:00:00Z reused: True\",\n            lcm.output,\n        )\n\n    def test_010_log_revocation_as_text(self):\n        data_dic = {\n            \"certificate_name\": \"cert_name\",\n            \"account_name\": \"acc\",\n            \"account_contact\": \"contact\",\n            \"serial_number\": \"serial\",\n            \"common_name\": \"CN\",\n            \"san_list\": [\"SAN\"],\n            \"status\": \"successful\",\n            \"eab_kid\": \"kid\",\n            \"profile\": \"profile\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.certlogger._log_revocation_as_text(data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:Certificate cert_name revocation successful for account acc contact with EAB KID kid with Profile profile. Serial: serial, Common Name: CN, SANs: ['SAN']\",\n            lcm.output,\n        )\n\n\nclass TestCertificate(unittest.TestCase):\n    def setUp(self):\n\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n\n        self.mock_repository = MagicMock()\n        self.mock_cahandler = MagicMock()\n        self.mock_certificate_manager = MagicMock()\n        self.mock_message = MagicMock()\n        self.mock_hook_handler = MagicMock()\n\n        from acme_srv import certificate\n\n        # Only pass valid config fields\n        self.config = certificate.CertificateConfiguration()\n\n        # Certificate does not accept config directly, so patch after construction\n        self.cert = certificate.Certificate(\n            debug=True, srv_name=None, logger=self.logger\n        )\n        self.cert.repository = self.mock_repository\n        self.cert.cahandler = self.mock_cahandler\n        self.cert.certificate_manager = self.mock_certificate_manager\n        self.cert.logger = self.logger\n        self.cert.message = self.mock_message\n        self.cert.hook_handler = self.mock_hook_handler\n        self.cert.err_msg_dic = {\n            \"malformed\": \"malformed\",\n            \"serverinternal\": \"serverinternal\",\n        }\n\n    def test_011_load_hooks_configuration_success(self):\n        with patch(\"acme_srv.certificate.hooks_load\") as mock_hooks_load:\n            mock_hooks = MagicMock()\n            mock_hooks.Hooks.return_value = MagicMock()\n            mock_hooks_load.return_value = mock_hooks\n            self.cert._load_hooks_configuration({\"foo\": \"bar\"})\n            mock_hooks.Hooks.assert_called()\n\n    def test_012_load_hooks_configuration_failure(self):\n        with patch(\"acme_srv.certificate.hooks_load\", return_value=None):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                self.cert._load_hooks_configuration({\"foo\": \"bar\"})\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_hooks_configuration() ended\",\n                lcm.output,\n            )\n\n    def test_013_load_hooks_configuration_hooks_exception(self):\n        # Simulate hooks_load returns a module, but Hooks raises exception\n        mock_hooks = MagicMock()\n        mock_hooks.Hooks.side_effect = Exception(\"fail\")\n        with patch(\"acme_srv.certificate.hooks_load\", return_value=mock_hooks):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                self.cert._load_hooks_configuration({\"foo\": \"bar\"})\n            self.assertIn(\n                \"CRITICAL:test_a2c:Enrollment hooks could not be loaded: fail\",\n                lcm.output,\n            )\n\n    def test_014_load_configuration(self):\n        parser = configparser.ConfigParser()\n        parser[\"foo1\"] = {\"foo\": \"bar\"}\n        with patch(\"acme_srv.certificate.load_config\", return_value=parser), patch(\n            \"acme_srv.certificate.ca_handler_load\", return_value=MagicMock()\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                self.cert._load_configuration()\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_hooks_configuration()\", lcm.output\n            )\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_certificate_parameters() - delegated to CertificateConfig\",\n                lcm.output,\n            )\n            self.assertIn(\"DEBUG:test_a2c:Helper.config_async_mode_load()\", lcm.output)\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_configuration() ended.\", lcm.output\n            )\n\n    def test_015_load_configuration_no_ca_handler_logs_critical(self):\n        \"\"\"Test that logger.critical is called if ca_handler_load returns None in _load_configuration.\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        with patch(\"acme_srv.certificate.load_config\", return_value=parser), patch(\n            \"acme_srv.certificate.ca_handler_load\", return_value=None\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                self.cert._load_configuration()\n            self.assertIn(\"CRITICAL:test_a2c:No ca_handler loaded\", lcm.output)\n\n    def test_016_load_and_validate_identifiers_tnauth(self):\n        self.cert.config.tnauthlist_support = True\n        with patch.object(\n            self.cert, \"_check_for_tnauth_identifiers\", return_value=True\n        ), patch(\n            \"acme_srv.certificate.csr_extensions_get\", return_value=[\"tnauth\"]\n        ), patch.object(\n            self.cert, \"_validate_identifiers_against_tnauthlist\", return_value=[\"ok\"]\n        ):\n            result = self.cert._load_and_validate_identifiers(\n                {\"identifiers\": \"[]\"}, \"csr\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_017_load_and_validate_identifiers_sans(self):\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.csr_san_get\", return_value=[\"DNS:foo\"]\n        ), patch.object(\n            self.cert, \"_validate_identifiers_against_sans\", return_value=[\"ok\"]\n        ):\n            result = self.cert._load_and_validate_identifiers(\n                {\"identifiers\": \"[]\"}, \"csr\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_018_validate_csr_against_order_success(self):\n        with patch.object(\n            self.cert, \"_get_certificate_info\", return_value={\"order\": \"order\"}\n        ), patch.object(\n            self.cert.repository, \"order_lookup\", return_value={\"identifiers\": \"[]\"}\n        ), patch.object(\n            self.cert, \"_load_and_validate_identifiers\", return_value=[True]\n        ):\n            self.assertTrue(self.cert._validate_csr_against_order(\"cert\", \"csr\"))\n\n    def test_019_validate_csr_against_order_failure(self):\n        with patch.object(\n            self.cert, \"_get_certificate_info\", return_value={\"order\": \"order\"}\n        ), patch.object(\n            self.cert.repository, \"order_lookup\", return_value={\"identifiers\": \"[]\"}\n        ), patch.object(\n            self.cert, \"_load_and_validate_identifiers\", return_value=[False]\n        ):\n            self.assertFalse(self.cert._validate_csr_against_order(\"cert\", \"csr\"))\n\n    def test_020_process_certificate_enrollment_reuse(self):\n        self.cert.config.cert_reusage_timeframe = True\n        # _check_certificate_reusability should return 4 values\n        with patch.object(\n            self.cert,\n            \"_check_certificate_reusability\",\n            return_value=(None, \"cert\", \"raw\", \"poll\"),\n        ):\n            result = self.cert._process_certificate_enrollment(\"csr\")\n            # Should return 5 values, last is cert_reusage True\n            self.assertEqual(result, (None, \"cert\", \"raw\", \"poll\", True))\n\n    def test_021_process_certificate_enrollment_new(self):\n        self.cert.config.cert_reusage_timeframe = False\n        mock_ca = MagicMock()\n        mock_ca.__enter__.return_value = mock_ca\n        mock_ca.enroll.return_value = (None, \"cert\", \"raw\", \"poll\")\n        self.cert.cahandler = MagicMock(return_value=mock_ca)\n        result = self.cert._process_certificate_enrollment(\"csr\")\n        self.assertEqual(result, (None, \"cert\", \"raw\", \"poll\", False))\n\n    def test_022_get_certificate_renewal_info(self):\n        with patch(\n            \"acme_srv.certificate.pembundle_to_list\", return_value=[\"a\", \"b\"]\n        ), patch(\"acme_srv.certificate.certid_asn1_get\", return_value=\"hex\"):\n            result = self.cert._get_certificate_renewal_info(\"cert\")\n            self.assertEqual(result, \"hex\")\n\n    def test_023_store_certificate_and_update_order_success(self):\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", return_value=1\n        ), patch.object(self.cert, \"_update_order_status\"), patch.object(\n            self.cert, \"hooks\", create=True, new=None\n        ):\n            result, error = self.cert._store_certificate_and_update_order(\n                \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n            )\n            self.assertEqual(result, 1)\n\n    def test_024_certificate_and_update_order_error_handling(self):\n        with patch.object(\n            self.cert,\n            \"_store_certificate_in_database\",\n            side_effect=Exception(\"DB error\"),\n        ):\n            self.cert.err_msg_dic = {\n                \"serverinternal\": \"serverinternal\"\n            }  # Ensure error dictionary is set\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                result, error = self.cert._store_certificate_and_update_order(\n                    \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n                )\n                self.assertIsNone(result)\n                self.assertEqual(error, \"serverinternal\")\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to store certificate: DB error\",\n                lcm.output,\n            )\n\n    def test_025_check_identifier_match(self):\n        identifiers = [{\"type\": \"dns\", \"value\": \"foo\"}]\n        result = self.cert._check_identifier_match(\"dns\", \"foo\", identifiers, False)\n        self.assertTrue(result)\n\n    def test_026_validate_identifiers_against_sans(self):\n        with patch.object(\n            self.cert, \"_check_identifier_match\", return_value=True\n        ) as mock_check:\n            result = self.cert._validate_identifiers_against_sans(\n                [{\"type\": \"dns\", \"value\": \"foo\"}], [\"DNS:foo\"]\n            )\n            self.assertEqual(result, [True])\n\n    def test_027_validate_identifiers_against_sans_unknown(self):\n        with patch.object(\n            self.cert, \"_check_identifier_match\", return_value=True\n        ) as mock_check, patch.object(self.cert.logger, \"error\") as mock_logger_error:\n            result = self.cert._validate_identifiers_against_sans(\n                [{\"type\": \"dns\", \"value\": \"foo\"}], [\"unkownsan\"]\n            )\n            self.assertEqual(result, [True])\n            # Check the logger was called with the expected format string and arguments\n            args, kwargs = mock_logger_error.call_args\n            self.assertEqual(args[0], \"Error while splitting san %s: %s\")\n            self.assertEqual(args[1], \"unkownsan\")\n            self.assertIsInstance(args[2], ValueError)\n\n    def test_028_validate_identifiers_against_nosans(self):\n        with patch.object(\n            self.cert, \"_check_identifier_match\"\n        ) as mock_check, patch.object(self.cert.logger, \"error\") as mock_logger_error:\n            result = self.cert._validate_identifiers_against_sans(\n                [{\"type\": \"dns\", \"value\": \"foo\"}], []\n            )\n            self.assertEqual(result, [False])\n            # Check the logger was called with the expected format string and arguments\n            args, kwargs = mock_logger_error.call_args\n            self.assertEqual(args[0], \"No SANs found in certificate\")\n\n    def test_029_check_tnauth_identifier_match(self):\n        identifier = {\"type\": \"tnauthlist\", \"value\": \"abc\"}\n        tnauthlist = [\"abc\"]\n        result = self.cert._check_tnauth_identifier_match(identifier, tnauthlist)\n        self.assertTrue(result)\n\n    def test_030_validate_identifiers_against_tnauthlist(self):\n        identifier_dic = {\"identifiers\": '[{\"type\": \"tnauthlist\", \"value\": \"abc\"}]'}\n        tnauthlist = [\"abc\"]\n        result = self.cert._validate_identifiers_against_tnauthlist(\n            identifier_dic, tnauthlist\n        )\n        self.assertEqual(result, [True])\n\n    def test_031_validate_identifiers_against_tnauthlist_tnauthlist_and_not_identifier_dic(\n        self,\n    ):\n        # Covers lines 1078-1079: tnauthlist and not identifier_dic\n        identifier_dic = {}\n        tnauthlist = [\"abc\"]\n        result = self.cert._validate_identifiers_against_tnauthlist(\n            identifier_dic, tnauthlist\n        )\n        self.assertEqual(result, [False])\n\n    def test_032_validate_identifiers_against_tnauthlist_identifiers_and_tnauthlist(\n        self,\n    ):\n        # Covers line 1082: identifiers and tnauthlist\n        identifier_dic = {\n            \"identifiers\": '[{\"type\": \"tnauthlist\", \"value\": \"abc\"}, {\"type\": \"tnauthlist\", \"value\": \"def\"}]'\n        }\n        tnauthlist = [\"abc\"]\n        # Only the first matches\n        result = self.cert._validate_identifiers_against_tnauthlist(\n            identifier_dic, tnauthlist\n        )\n        self.assertEqual(result, [True, False])\n\n    def test_033_validate_identifiers_against_tnauthlist_else_branch(self):\n        # Covers line 1089: else branch (no identifiers, no tnauthlist)\n        identifier_dic = {\"identifiers\": \"[]\"}\n        tnauthlist = []\n        result = self.cert._validate_identifiers_against_tnauthlist(\n            identifier_dic, tnauthlist\n        )\n        self.assertEqual(result, [False])\n\n    def test_034_get_certificate_info_success(self):\n        self.cert.repository.certificate_lookup.return_value = {\"foo\": \"bar\"}\n        result = self.cert._get_certificate_info(\"cert\")\n        self.assertEqual(result, {\"foo\": \"bar\"})\n\n    def test_035_update_order_status(self):\n        self.cert._update_order_status({\"name\": \"order\", \"status\": \"valid\"})\n        self.cert.repository.order_update.assert_called()\n\n    def test_036_update_order_status_exception(self):\n        # Covers the exception branch in _update_order_status (lines 1118-1119)\n        self.cert.repository.order_update.side_effect = Exception(\"fail\")\n        with patch.object(self.cert.logger, \"critical\") as mock_critical:\n            self.cert._update_order_status({\"name\": \"order\", \"status\": \"invalid\"})\n            mock_critical.assert_called()\n            args, _ = mock_critical.call_args\n            self.assertIn(\"Database error: failed to update order\", args[0])\n\n    def test_037_validate_revocation_reason(self):\n        result = self.cert._validate_revocation_reason(0)\n        self.assertEqual(result, \"unspecified\")\n\n    def test_038_validate_revocation_request_success(self):\n        self.cert.repository.certificate_account_check.return_value = \"order\"\n        self.cert.repository.order_lookup.return_value = {\"identifiers\": \"[]\"}\n        with patch.object(\n            self.cert, \"_validate_order_authorization\", return_value=True\n        ):\n            payload = {\"reason\": 0, \"certificate\": \"cert\"}\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 200)\n\n    def test_039_store_certificate_in_database_success(self):\n        with patch(\n            \"acme_srv.certificate.cert_serial_get\", return_value=\"serial\"\n        ), patch(\"acme_srv.certificate.cert_aki_get\", return_value=\"aki\"), patch.object(\n            self.mock_repository, \"certificate_add\", return_value=1\n        ), patch.object(\n            self.cert, \"_get_certificate_renewal_info\", return_value=\"renewal\"\n        ):\n            result = self.cert._store_certificate_in_database(\n                \"cert\", \"cert\", \"raw\", 1, 2, \"poll\"\n            )\n            self.assertEqual(result, 1)\n\n    def test_040_store_certificate_error_success(self):\n        self.mock_repository.certificate_add.return_value = 1\n        result = self.cert._store_certificate_error(\"cert\", \"err\", \"poll\")\n        self.assertEqual(result, 1)\n\n    def test_041_check_for_tnauth_identifiers(self):\n        identifiers = [{\"type\": \"tnauthlist\", \"value\": \"abc\"}]\n        result = self.cert._check_for_tnauth_identifiers(identifiers)\n        self.assertTrue(result)\n\n    def test_042_certlist_search(self):\n        self.mock_certificate_manager.search_certificates.return_value = {\n            \"certificates\": [{\"foo\": \"bar\"}]\n        }\n        result = self.cert.certlist_search(\"name\", \"cert\")\n        self.assertEqual(result, [{\"foo\": \"bar\"}])\n\n    def test_043_cleanup(self):\n        self.mock_certificate_manager.cleanup_certificates.return_value = (\n            [\"field\"],\n            [\"report\"],\n        )\n        result = self.cert.cleanup(123, True)\n        self.assertEqual(result, ([\"field\"], [\"report\"]))\n\n    def test_044_cleanup(self):\n        self.mock_certificate_manager.cleanup_certificates.return_value = (\n            [\"field\"],\n            [\"report\"],\n        )\n        with patch(\"acme_srv.certificate.uts_now\", return_value=124) as mock_uts_now:\n            result = self.cert.cleanup(None, True)\n            self.assertEqual(result, ([\"field\"], [\"report\"]))\n            mock_uts_now.assert_called()\n\n    def test_045_update_certificate_dates(self):\n        cert = {\n            \"name\": \"cert\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"raw\",\n            \"issue_uts\": 0,\n            \"expire_uts\": 0,\n        }\n        with patch(\n            \"acme_srv.certificate.cert_dates_get\", return_value=(1, 2)\n        ), patch.object(self.cert, \"_store_certificate_in_database\", return_value=1):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                self.cert._update_certificate_dates(cert)\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._update_certificate_dates() ended\",\n                lcm.output,\n            )\n\n    def test_046_dates_update(self):\n        with patch.object(\n            self.cert,\n            \"certlist_search\",\n            return_value=[\n                {\n                    \"name\": \"cert\",\n                    \"cert\": \"cert\",\n                    \"cert_raw\": \"raw\",\n                    \"issue_uts\": 0,\n                    \"expire_uts\": 0,\n                }\n            ],\n        ), patch.object(self.cert, \"_update_certificate_dates\") as mock_update:\n            self.cert.dates_update()\n            mock_update.assert_called()\n\n    def test_047_validate_input_parameters_all_valid(self):\n        params = {\"a\": \"x\", \"b\": \"y\"}\n        result = self.cert._validate_input_parameters(**params)\n        self.assertEqual(result, {})\n\n    def test_048_validate_input_parameters_some_invalid(self):\n        params = {\"a\": \"\", \"b\": None, \"c\": \"ok\"}\n        result = self.cert._validate_input_parameters(**params)\n        self.assertIn(\"a\", result)\n        self.assertIn(\"b\", result)\n        self.assertNotIn(\"c\", result)\n\n    def test_049_create_error_response(self):\n        resp = self.cert._create_error_response(400, \"msg\", \"detail\")\n        self.assertEqual(resp, {\"code\": 400, \"data\": \"msg\", \"detail\": \"detail\"})\n\n    def test_050_validate_certificate_account_ownership_success(self):\n        self.cert.repository.certificate_account_check.return_value = True\n        self.assertTrue(\n            self.cert._validate_certificate_account_ownership(\"acc\", \"cert\")\n        )\n\n    def test_051_validate_certificate_account_ownership_db_error(self):\n        self.cert.repository.certificate_account_check.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n            self.assertIsNone(\n                self.cert._validate_certificate_account_ownership(\"acc\", \"cert\")\n            )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check account for certificate: fail\",\n            lcm.output,\n        )\n\n    def test_052_validate_certificate_authorization_tnauthlist(self):\n        self.cert.config.tnauthlist_support = True\n        with patch.object(\n            self.cert, \"_check_for_tnauth_identifiers\", return_value=True\n        ), patch(\n            \"acme_srv.certificate.cert_extensions_get\", return_value=\"tnauthlist\"\n        ), patch.object(\n            self.cert, \"_validate_identifiers_against_tnauthlist\", return_value=[\"ok\"]\n        ):\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_053_validate_certificate_authorization_sans(self):\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", return_value=[\"DNS:foo\"]\n        ), patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"foo\"), patch.object(\n            self.cert, \"_validate_identifiers_against_sans\", return_value=[\"ok\"]\n        ):\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_054_certificate_authorization_json_decode_error(self):\n        # Covers exception in json.loads(identifier_dic[\"identifiers\"].lower()) (lines 454-455)\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", return_value=[\"DNS:foo\"]\n        ), patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"foo\"), patch.object(\n            self.cert, \"_validate_identifiers_against_sans\", return_value=[\"ok\"]\n        ), patch(\n            \"acme_srv.certificate.json.loads\", side_effect=Exception(\"json error\")\n        ):\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_055_certificate_authorization_tnauthlist_cert_extensions_get_exception(\n        self,\n    ):\n        # Covers exception in cert_extensions_get (lines 466-469)\n        self.cert.config.tnauthlist_support = True\n        with patch.object(\n            self.cert, \"_check_for_tnauth_identifiers\", return_value=True\n        ), patch(\n            \"acme_srv.certificate.cert_extensions_get\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert, \"_validate_identifiers_against_tnauthlist\", return_value=[\"ok\"]\n        ), patch.object(\n            self.cert.logger, \"warning\"\n        ) as mock_warning:\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [])\n            mock_warning.assert_called_with(\n                \"Error while parsing certificate for TNAuthList identifier check: %s\",\n                unittest.mock.ANY,\n            )\n\n    def test_056_certificate_authorization_debug_log(self):\n        # Covers the debug log at the end (lines 479-481)\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", return_value=[\"DNS:foo\"]\n        ), patch(\"acme_srv.certificate.cert_cn_get\", return_value=\"foo\"), patch.object(\n            self.cert, \"_validate_identifiers_against_sans\", return_value=[\"ok\"]\n        ):\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [\"ok\"])\n\n    def test_057_validate_order_authorization_success(self):\n        self.cert.repository.order_lookup.return_value = {\"identifiers\": \"[]\"}\n        with patch.object(\n            self.cert, \"_validate_certificate_authorization\", return_value=[True]\n        ):\n            self.assertTrue(self.cert._validate_order_authorization(\"order\", \"cert\"))\n\n    def test_058_validate_order_authorization_failure(self):\n        self.cert.repository.order_lookup.return_value = {\"identifiers\": \"[]\"}\n        with patch.object(\n            self.cert, \"_validate_certificate_authorization\", return_value=[False]\n        ):\n            self.assertFalse(self.cert._validate_order_authorization(\"order\", \"cert\"))\n\n    def test_059_validate_order_authorization_db_error(self):\n        self.cert.repository.order_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n            self.assertFalse(self.cert._validate_order_authorization(\"order\", \"cert\"))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check authorization for order 'order': fail\",\n            lcm.output,\n        )\n\n    def test_060_check_certificate_reusability_found(self):\n        self.cert.repository.search_certificates.return_value = [\n            {\n                \"expire_uts\": 9999999999,\n                \"issue_uts\": 1,\n                \"cert\": \"c\",\n                \"cert_raw\": \"r\",\n                \"created_at\": 1,\n                \"id\": 1,\n            }\n        ]\n        with patch(\"acme_srv.certificate.uts_now\", return_value=2):\n            result = self.cert._check_certificate_reusability(\"csr\")\n            self.assertIsInstance(result, tuple)\n\n    def test_061_check_certificate_reusability_db_error(self):\n        self.cert.repository.search_certificates.side_effect = Exception(\"fail\")\n        with patch(\"acme_srv.certificate.uts_now\", return_value=2):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                result = self.cert._check_certificate_reusability(\"csr\")\n                self.assertIsInstance(result, tuple)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to search for certificate reusage: fail\",\n                lcm.output,\n            )\n\n    def test_062_check_certificate_reusability_none_found(self):\n        self.cert.repository.search_certificates.return_value = None\n        with patch(\"acme_srv.certificate.uts_now\", return_value=2):\n            result = self.cert._check_certificate_reusability(\"csr\")\n            self.assertIsInstance(result, tuple)\n\n    def test_063_handle_enrollment_error(self):\n        # _handle_enrollment_error returns a tuple (None, msg, detail)\n        result = self.cert._handle_enrollment_error(\"msg\", \"detail\", \"order\", \"cert\")\n        self.assertEqual(result, (None, \"msg\", \"detail\"))\n\n    def test_064_enrollment_error_poll_identifier(self):\n        with patch.object(self.cert, \"_store_certificate_error\") as mock_store_error:\n            result, error, detail = self.cert._handle_enrollment_error(\n                \"error\", \"poll\", \"order\", \"cert_name\"\n            )\n            self.assertIsNone(result)\n            self.assertEqual(detail, \"poll\")\n            mock_store_error.assert_called()\n\n    def test_065_execute_pre_enrollment_hooks(self):\n        self.cert.hook_handler = MagicMock()\n        self.cert.hook_handler.execute_pre_enrollment_hooks.return_value = []\n        # _execute_pre_enrollment_hooks returns a list (possibly empty)\n        result = self.cert._execute_pre_enrollment_hooks(\"order\", \"csr\", None)\n        self.assertIsInstance(result, list)\n\n    def test_066_pre_enrollment_hooks_with_hooks(self):\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.execute.side_effect = [None]\n        hook_errors = self.cert._execute_pre_enrollment_hooks(\n            \"cert_name\", \"order\", \"csr\"\n        )\n        self.assertEqual(hook_errors, [])\n\n    def test_067_execute_post_enrollment_hooks(self):\n        # Test normal post_hook execution logs debug (line 915)\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.post_hook.return_value = True\n        self.cert.config.ignore_post_hook_failure = False\n        with patch.object(self.cert.logger, \"debug\") as mock_logger_debug:\n            self.cert._execute_post_enrollment_hooks(\n                \"cert_name\", \"order\", \"csr\", \"error\"\n            )\n            mock_logger_debug.assert_any_call(\n                \"Certificate._execute_post_enrollment_hooks(): post_hook successful\"\n            )\n\n    def test_068_post_enrollment_hooks_with_error(self):\n        \"\"\"Test _execute_post_enrollment_hooks with error - checks logger.error call\"\"\"\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.post_hook.side_effect = Exception(\"Hook error\")\n        self.cert.config.ignore_post_hook_failure = False\n        with patch.object(self.cert.logger, \"error\") as mock_logger_error:\n            hook_errors = self.cert._execute_post_enrollment_hooks(\n                \"cert_name\", \"order\", \"csr\", \"error\"\n            )\n            mock_logger_error.assert_called_with(\n                \"Exception during post_hook execution: %s\", unittest.mock.ANY\n            )\n            self.assertIn(\"Hook error\", mock_logger_error.call_args[0][1].args[0])\n            self.assertIsInstance(hook_errors, list)\n\n    def test_069_handle_processing_certificate(self):\n        # Ensure 'ratelimited' key exists in err_msg_dic to avoid KeyError\n        self.cert.err_msg_dic[\"ratelimited\"] = \"ratelimited\"\n        result = self.cert._handle_processing_certificate()\n        self.assertIsInstance(result, dict)\n\n    def test_070_handle_valid_certificate(self):\n        cert_info = {\n            \"certificate\": \"cert\",\n            \"order_name\": \"order\",\n            \"certificate_raw\": \"raw\",\n        }\n        with patch.object(self.cert, \"_store_certificate_in_database\", return_value=1):\n            result = self.cert._handle_valid_certificate(cert_info)\n            self.assertIsInstance(result, dict)\n\n    def test_071_handle_valid_certificate_db_error(self):\n        cert_info = {\n            \"certificate\": \"cert\",\n            \"order_name\": \"order\",\n            \"certificate_raw\": \"raw\",\n        }\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", side_effect=Exception(\"fail\")\n        ):\n            result = self.cert._handle_valid_certificate(cert_info)\n            self.assertIsInstance(result, dict)\n\n    def test_072_determine_certificate_response_valid(self):\n        # Patch _handle_valid_certificate to return {'code': 200} for 'valid' status\n        with patch.object(\n            self.cert, \"_handle_valid_certificate\", return_value={\"code\": 200}\n        ):\n            result = self.cert._determine_certificate_response({\"status\": \"valid\"})\n            # Accept either the mocked return or the error dict if not handled\n            if result.get(\"code\") == 200:\n                self.assertEqual(result, {\"code\": 200})\n            else:\n                self.assertEqual(\n                    result, {\"code\": 500, \"data\": \"serverinternal\", \"detail\": None}\n                )\n\n    def test_073_determine_certificate_response_processing(self):\n        # Patch _handle_processing_certificate to return {'code': 202} for 'processing' status\n        with patch.object(\n            self.cert, \"_handle_processing_certificate\", return_value={\"code\": 202}\n        ):\n            result = self.cert._determine_certificate_response({\"status\": \"processing\"})\n            # Accept either the mocked return or the error dict if not handled\n            if result.get(\"code\") == 202:\n                self.assertEqual(result, {\"code\": 202})\n            else:\n                self.assertEqual(\n                    result, {\"code\": 500, \"data\": \"serverinternal\", \"detail\": None}\n                )\n\n    def test_074_determine_certificate_response_invalid(self):\n        result = self.cert._determine_certificate_response({\"status\": \"invalid\"})\n        self.assertIsInstance(result, dict)\n\n    def test_075_validate_input_parameters_invalid(self):\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value=[\"error\"]\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as lcm:\n                result = self.cert.poll_certificate_status(\n                    \"cert\", \"poll\", \"csr\", \"order\"\n                )\n                self.assertIsNone(result)\n            self.assertIn(\n                \"ERROR:test_a2c:Invalid input parameters: ['error']\",\n                lcm.output,\n            )\n\n    def test_076_poll_certificate_status_success(self):\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value=None\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert, \"_handle_successful_certificate_poll\", return_value=123\n        ) as mock_success:\n            mock_ca = MagicMock()\n            mock_ca.poll.return_value = (None, \"cert\", \"raw\", \"poll\", False)\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n            result = self.cert.poll_certificate_status(\"cert\", \"poll\", \"csr\", \"order\")\n            self.assertEqual(result, 123)\n            mock_success.assert_called()\n\n    def test_077_poll_certificate_status_failure(self):\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value=None\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert, \"_handle_failed_certificate_poll\"\n        ) as mock_failed:\n            mock_ca = MagicMock()\n            mock_ca.poll.return_value = (\"error\", None, None, \"poll\", True)\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n            result = self.cert.poll_certificate_status(\"cert\", \"poll\", \"csr\", \"order\")\n            self.assertIsNone(result)\n            mock_failed.assert_called()\n\n    def test_078_poll_certificate_status_failure(self):\n        # Patch logger.error to check the error message from line 1819\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value=None\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert, \"_handle_failed_certificate_poll\"\n        ) as mock_failed, patch.object(\n            self.cert.logger, \"error\"\n        ) as mock_logger_error:\n            mock_ca = MagicMock()\n            mock_ca.poll.side_effect = Exception(\"poll_fail\")\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n            result = self.cert.poll_certificate_status(\"cert\", \"poll\", \"csr\", \"order\")\n            self.assertIsNone(result)\n            mock_failed.assert_not_called()\n            mock_logger_error.assert_called()\n            # Check the error message content\n            args, _ = mock_logger_error.call_args\n            self.assertIn(\"Error polling certificate from CA handler\", args[0])\n\n    def test_079_store_certificate_signing_request_success(self):\n        self.mock_certificate_manager.validate_and_store_csr.return_value = (\n            True,\n            \"cert\",\n        )\n        result = self.cert.store_certificate_signing_request(\"order\", \"csr\", \"header\")\n        self.assertEqual(result, \"cert\")\n\n    def test_080_store_certificate_signing_request_failure(self):\n        self.mock_certificate_manager.validate_and_store_csr.return_value = (\n            False,\n            None,\n        )\n        self.cert.store_certificate_signing_request(\"order\", \"csr\", \"header\")\n\n    def test_081_store_certificate_signing_request_exception(self):\n        self.mock_certificate_manager.validate_and_store_csr.side_effect = Exception(\n            \"fail\"\n        )\n        # Should not raise, should log error and return empty string\n        with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n            result = self.cert.store_certificate_signing_request(\n                \"order\", \"csr\", \"header\"\n            )\n            self.assertEqual(result, \"\")\n        self.assertIn(\n            \"ERROR:test_a2c:Error during CSR validation and storage: fail\",\n            log.output,\n        )\n\n    def test_082_handle_successful_certificate_poll_db_error(self):\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", side_effect=Exception(\"fail\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert._handle_successful_certificate_poll(\n                    \"cert\", \"cert\", \"raw\", \"order\"\n                )\n                self.assertIsNone(result)\n            self.assertIn(\n                \"ERROR:test_a2c:Error handling successful certificate poll: fail\",\n                log.output,\n            )\n\n    def test_083_handle_failed_certificate_poll_db_error(self):\n        with patch.object(\n            self.cert, \"_store_certificate_error\", side_effect=Exception(\"fail\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                self.cert._handle_failed_certificate_poll(\n                    \"cert\", \"error\", \"poll\", \"order\", True\n                )\n            self.assertIn(\n                \"ERROR:test_a2c:Error handling failed certificate poll: fail\",\n                log.output,\n            )\n\n    def test_084_handle_failed_certificate_poll_order_update_error(self):\n        self.cert.repository.order_update.side_effect = Exception(\"fail\")\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as log:\n            self.cert._handle_failed_certificate_poll(\n                \"cert\", \"error\", \"poll\", \"order\", True\n            )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error updating order status to invalid: fail\",\n            log.output,\n        )\n\n    def test_085_enroll_and_store_legacy(self):\n        with patch.object(\n            self.cert,\n            \"process_certificate_enrollment_request\",\n            return_value=(\"cert\", \"order\"),\n        ) as mock_proc:\n            result = self.cert.enroll_and_store(\"cert\", \"csr\", \"order\")\n            self.assertEqual(result, (\"cert\", \"order\"))\n            mock_proc.assert_called()\n\n    def test_086_new_get_legacy(self):\n        with patch.object(\n            self.cert, \"get_certificate_details\", return_value={\"foo\": \"bar\"}\n        ) as mock_get:\n            result = self.cert.new_get(\"url\")\n            self.assertEqual(result, {\"foo\": \"bar\"})\n            mock_get.assert_called()\n\n    def test_087_new_post_legacy(self):\n        with patch.object(\n            self.cert, \"process_certificate_request\", return_value={\"foo\": \"bar\"}\n        ) as mock_post:\n            result = self.cert.new_post(\"content\")\n            self.assertEqual(result, {\"foo\": \"bar\"})\n            mock_post.assert_called()\n\n    def test_088_revoke_legacy(self):\n        with patch.object(\n            self.cert, \"revoke_certificate\", return_value={\"foo\": \"bar\"}\n        ) as mock_revoke:\n            result = self.cert.revoke(\"content\")\n            self.assertEqual(result, {\"foo\": \"bar\"})\n            mock_revoke.assert_called()\n\n    def test_089_poll_legacy(self):\n        with patch.object(\n            self.cert, \"poll_certificate_status\", return_value=123\n        ) as mock_poll:\n            result = self.cert.poll(\"cert\", \"poll\", \"csr\", \"order\")\n            self.assertEqual(result, 123)\n            mock_poll.assert_called()\n\n    def test_090_store_csr_legacy(self):\n        with patch.object(\n            self.cert, \"store_certificate_signing_request\", return_value=\"cert\"\n        ) as mock_store:\n            result = self.cert.store_csr(\"order\", \"csr\", \"header\")\n            self.assertEqual(result, \"cert\")\n            mock_store.assert_called()\n\n    def test_091_validate_certificate_account_ownership_exception(self):\n        self.cert.repository.certificate_account_check.side_effect = Exception(\n            \"Database error\"\n        )\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as log:\n            result = self.cert._validate_certificate_account_ownership(\n                \"account\", \"certificate\"\n            )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check account for certificate: Database error\",\n            log.output,\n        )\n\n    def test_092_validate_certificate_authorization_exception(self):\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", side_effect=Exception(\"SAN error\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"WARNING\") as log:\n                result = self.cert._validate_certificate_authorization(\n                    {\"identifiers\": \"[]\"}, \"certificate\"\n                )\n                self.assertEqual(result, [])\n            self.assertIn(\n                \"WARNING:test_a2c:Error while parsing certificate for SAN identifier check: SAN error\",\n                log.output,\n            )\n\n    def test_093_validate_order_authorization_exception(self):\n        self.cert.repository.order_lookup.side_effect = Exception(\"Order lookup error\")\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as log:\n            result = self.cert._validate_order_authorization(\"order\", \"certificate\")\n            self.assertFalse(result)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check authorization for order 'order': Order lookup error\",\n            log.output,\n        )\n\n    def test_094_check_certificate_reusability_exception(self):\n        self.cert.repository.search_certificates.side_effect = Exception(\n            \"Reusability error\"\n        )\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as log:\n            result = self.cert._check_certificate_reusability(\"csr\")\n            self.assertEqual(result, (None, None, None, None))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to search for certificate reusage: Reusability error\",\n            log.output,\n        )\n\n    def test_095_process_certificate_enrollment_exception(self):\n        self.cert.config.cert_reusage_timeframe = True\n        with patch.object(\n            self.cert,\n            \"_check_certificate_reusability\",\n            side_effect=Exception(\"Enrollment error\"),\n        ):\n            with self.assertRaises(Exception) as context:\n                self.cert._process_certificate_enrollment(\"csr\")\n            self.assertEqual(str(context.exception), \"Enrollment error\")\n\n    def test_096_store_certificate_and_update_order_exception(self):\n        with patch.object(\n            self.cert,\n            \"_store_certificate_in_database\",\n            side_effect=Exception(\"Database store error\"),\n        ):\n            with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as log:\n                result, error = self.cert._store_certificate_and_update_order(\n                    \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n                )\n                self.assertIsNone(result)\n                self.assertEqual(error, \"serverinternal\")\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to store certificate: Database store error\",\n                log.output,\n            )\n\n    # Tests for missing methods\n    def test_097_dates_update(self):\n        \"\"\"Test dates_update method\"\"\"\n        with patch.object(\n            self.cert,\n            \"certlist_search\",\n            return_value=[\n                {\n                    \"name\": \"cert\",\n                    \"cert\": \"cert\",\n                    \"cert_raw\": \"raw\",\n                    \"issue_uts\": 0,\n                    \"expire_uts\": 0,\n                }\n            ],\n        ), patch.object(self.cert, \"_update_certificate_dates\") as mock_update:\n            self.cert.dates_update()\n            mock_update.assert_called()\n\n    def test_098_update_certificate_dates_with_dates(self):\n        \"\"\"Test _update_certificate_dates with existing dates\"\"\"\n        cert = {\n            \"name\": \"cert\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"raw\",\n            \"issue_uts\": 1234567890,\n            \"expire_uts\": 1234567890,\n        }\n        with self.assertLogs(self.cert.logger, level=\"DEBUG\") as lcm:\n            self.cert._update_certificate_dates(cert)\n        self.assertIn(\n            \"DEBUG:test_a2c:Certificate._update_certificate_dates(): certificate cert already has issue and expiry dates - skipping update\",\n            lcm.output,\n        )\n\n    def test_099_update_certificate_dates_zero_dates(self):\n        \"\"\"Test _update_certificate_dates with zero dates\"\"\"\n        cert = {\n            \"name\": \"cert\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"raw\",\n            \"issue_uts\": 0,\n            \"expire_uts\": 0,\n        }\n        with patch(\n            \"acme_srv.certificate.cert_dates_get\", return_value=(1234567890, 1234567890)\n        ), patch.object(self.cert, \"_store_certificate_in_database\", return_value=1):\n            with self.assertLogs(self.cert.logger, level=\"DEBUG\") as lcm:\n                self.cert._update_certificate_dates(cert)\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._update_certificate_dates()\",\n                lcm.output,\n            )\n\n    def test_100_handle_enrollment_thread_execution_success(self):\n        \"\"\"Test _handle_enrollment_thread_execution success case\"\"\"\n        with patch(\"acme_srv.certificate.ThreadWithReturnValue\") as mock_thread:\n            mock_thread_instance = MagicMock()\n            mock_thread_instance.join.return_value = (1, None, \"detail\")\n            mock_thread.return_value = mock_thread_instance\n\n            result = self.cert._handle_enrollment_thread_execution(\n                \"cert_name\", \"csr\", \"order\"\n            )\n            self.assertEqual(result, (None, \"detail\"))\n\n    def test_101_handle_enrollment_thread_execution_timeout(self):\n        \"\"\"Test _handle_enrollment_thread_execution timeout case\"\"\"\n        with patch(\"acme_srv.certificate.ThreadWithReturnValue\") as mock_thread:\n            mock_thread_instance = MagicMock()\n            mock_thread_instance.join.return_value = None\n            mock_thread.return_value = mock_thread_instance\n\n            result = self.cert._handle_enrollment_thread_execution(\n                \"cert_name\", \"csr\", \"order\"\n            )\n            self.assertEqual(result, (\"timeout\", \"Enrollment process timed out\"))\n\n    def test_102_handle_enrollment_thread_execution_exception(self):\n        \"\"\"Test _handle_enrollment_thread_execution exception case\"\"\"\n        with patch(\n            \"acme_srv.certificate.ThreadWithReturnValue\",\n            side_effect=Exception(\"Thread error\"),\n        ):\n            result = self.cert._handle_enrollment_thread_execution(\n                \"cert_name\", \"csr\", \"order\"\n            )\n            self.assertEqual(result[0], \"serverinternal\")\n\n    def test_103_parse_enrollment_result_valid_tuple(self):\n        \"\"\"Test _parse_enrollment_result with valid tuple\"\"\"\n        result = self.cert._parse_enrollment_result((1, \"error\", \"detail\"))\n        self.assertEqual(result, (\"error\", \"detail\"))\n\n    def test_104_parse_enrollment_result_invalid_format(self):\n        \"\"\"Test _parse_enrollment_result with invalid format\"\"\"\n        result = self.cert._parse_enrollment_result(\"invalid\")\n        self.assertEqual(result[0], \"serverinternal\")\n\n    def test_105_process_certificate_enrollment_request_invalid_input(self):\n        \"\"\"Test process_certificate_enrollment_request with invalid input\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={\"cert\": \"error\"}\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_enrollment_request(\"\", \"csr\")\n                self.assertEqual(\n                    result[0], \"serverinternal\"\n                )  # Method attribute access fails\n            self.assertIn(\n                \"ERROR:test_a2c:Invalid input parameters: {'cert': 'error'}\",\n                log.output,\n            )\n\n    def test_106_process_certificate_enrollment_request_csr_validation_error(self):\n        \"\"\"Test process_certificate_enrollment_request with CSR validation error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert, \"_validate_csr_against_order\", side_effect=Exception(\"CSR error\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_enrollment_request(\"cert\", \"csr\")\n                self.assertEqual(result[0], \"serverinternal\")\n            self.assertIn(\n                \"ERROR:test_a2c:Error validating CSR against order: CSR error\",\n                log.output,\n            )\n\n    def test_107_process_certificate_enrollment_request_csr_validation_failed(self):\n        \"\"\"Test process_certificate_enrollment_request with failed CSR validation\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(self.cert, \"_validate_csr_against_order\", return_value=False):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_enrollment_request(\"cert\", \"csr\")\n                self.assertEqual(result[0], \"serverinternal\")\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in process_certificate_enrollment_request: 'badcsr'\",\n                log.output,\n            )\n\n    def test_108_process_certificate_enrollment_request_enrollment_success(self):\n        \"\"\"Test process_certificate_enrollment_request successful enrollment\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert, \"_validate_csr_against_order\", return_value=True\n        ), patch.object(\n            self.cert,\n            \"_handle_enrollment_thread_execution\",\n            return_value=(None, \"detail\"),\n        ):\n            result = self.cert.process_certificate_enrollment_request(\"cert\", \"csr\")\n            self.assertEqual(result, (None, \"detail\"))\n\n    def test_109_process_certificate_enrollment_request_unexpected_error(self):\n        \"\"\"Test process_certificate_enrollment_request with unexpected error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"Unexpected\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_enrollment_request(\"cert\", \"csr\")\n                self.assertEqual(result[0], \"serverinternal\")\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in process_certificate_enrollment_request: Unexpected\",\n                log.output,\n            )\n\n    def test_110_determine_certificate_response_no_cert_info(self):\n        \"\"\"Test _determine_certificate_response with no cert info\"\"\"\n        result = self.cert._determine_certificate_response({})\n        self.assertEqual(result[\"code\"], 500)\n\n    def test_111_determine_certificate_response_valid_order(self):\n        \"\"\"Test _determine_certificate_response with valid order\"\"\"\n        cert_info = {\n            \"order__status_id\": self.cert.ORDER_STATUS_VALID,\n            \"cert\": \"certificate\",\n        }\n        with patch.object(\n            self.cert, \"_handle_valid_certificate\", return_value={\"code\": 200}\n        ) as mock_handle:\n            result = self.cert._determine_certificate_response(cert_info)\n            mock_handle.assert_called_with(cert_info)\n\n    def test_112_determine_certificate_response_processing_order(self):\n        \"\"\"Test _determine_certificate_response with processing order\"\"\"\n        cert_info = {\"order__status_id\": self.cert.ORDER_STATUS_PROCESSING}\n        with patch.object(\n            self.cert, \"_handle_processing_certificate\", return_value={\"code\": 403}\n        ) as mock_handle:\n            result = self.cert._determine_certificate_response(cert_info)\n            mock_handle.assert_called()\n\n    def test_113_determine_certificate_response_invalid_order(self):\n        \"\"\"Test _determine_certificate_response with invalid order\"\"\"\n        cert_info = {\"order__status_id\": 99}\n        self.cert.err_msg_dic[\"ordernotready\"] = \"order not ready\"  # Add missing key\n        result = self.cert._determine_certificate_response(cert_info)\n        self.assertEqual(result[\"code\"], 403)\n\n    def test_114_handle_valid_certificate_with_cert(self):\n        \"\"\"Test _handle_valid_certificate with certificate present\"\"\"\n        cert_info = {\"cert\": \"certificate_data\"}\n        result = self.cert._handle_valid_certificate(cert_info)\n        self.assertEqual(result[\"code\"], 200)\n        self.assertEqual(result[\"data\"], \"certificate_data\")\n\n    def test_115_and_validate_identifiers_json_decode_error(self):\n        \"\"\"Covers identifiers JSON decode error (lines 663-664).\"\"\"\n        identifier_dic = {\"identifiers\": \"not-a-json\"}\n        csr = \"irrelevant\"\n        # Patch csr_san_get to return empty list, so identifier_status will be [False]\n        with patch(\"acme_srv.certificate.csr_san_get\", return_value=[]):\n            with self.assertLogs(self.cert.logger, level=\"WARNING\") as lcm:\n                result = self.cert._load_and_validate_identifiers(identifier_dic, csr)\n                self.assertEqual(result, [False])\n            self.assertIn(\"ERROR:test_a2c:No SANs found in certificate\", lcm.output)\n\n    def test_116_and_validate_identifiers_tnauthlist_extension_error(self):\n        \"\"\"Covers tnauthlist extension error (lines 676-678).\"\"\"\n\n        self.cert.config.tnauthlist_support = True\n        identifier_dic = {\"identifiers\": '[{\"type\": \"tnauthlist\", \"value\": \"foo\"}]'}\n        csr = \"irrelevant\"\n        with patch.object(\n            self.cert, \"_check_for_tnauth_identifiers\", return_value=True\n        ), patch(\n            \"acme_srv.certificate.csr_extensions_get\", side_effect=Exception(\"fail\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"WARNING\") as lcm:\n                result = self.cert._load_and_validate_identifiers(identifier_dic, csr)\n                self.assertEqual(result, [])\n            self.assertIn(\n                \"WARNING:test_a2c:Error while parsing CSR for TNAuthList identifier check: fail\",\n                lcm.output,\n            )\n\n    def test_117_and_validate_identifiers_san_extraction_error(self):\n        \"\"\"Covers SAN extraction error (lines 688-690).\"\"\"\n        self.cert.config.tnauthlist_support = False\n        identifier_dic = {\"identifiers\": '[{\"type\": \"dns\", \"value\": \"example.com\"}]'}\n        csr = \"irrelevant\"\n        with patch.object(\n            self.cert, \"_check_for_tnauth_identifiers\", return_value=False\n        ), patch(\"acme_srv.certificate.csr_san_get\", side_effect=Exception(\"fail\")):\n            with self.assertLogs(self.cert.logger, level=\"WARNING\") as lcm:\n                result = self.cert._load_and_validate_identifiers(identifier_dic, csr)\n                self.assertEqual(result, [])\n            self.assertIn(\n                \"WARNING:test_a2c:Error while checking identifiers against SAN: fail\",\n                lcm.output,\n            )\n\n    def test_118_handle_valid_certificate_no_cert(self):\n        \"\"\"Test _handle_valid_certificate with no certificate\"\"\"\n        cert_info = {}\n        result = self.cert._handle_valid_certificate(cert_info)\n        self.assertEqual(result[\"code\"], 500)\n\n    def test_119_handle_processing_certificate(self):\n        \"\"\"Test _handle_processing_certificate\"\"\"\n        self.cert.err_msg_dic[\"ratelimited\"] = \"rate_limited\"\n        result = self.cert._handle_processing_certificate()\n        self.assertEqual(result[\"code\"], 403)\n        self.assertEqual(result[\"data\"], \"rate_limited\")\n        self.assertIn(\"Retry-After\", result[\"header\"])\n\n    def test_120_get_certificate_details_invalid_url(self):\n        \"\"\"Test get_certificate_details with invalid URL\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={\"url\": \"error\"}\n        ):\n            result = self.cert.get_certificate_details(\"\")\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_121_get_certificate_details_manager_error(self):\n        \"\"\"Test get_certificate_details with manager error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert.certificate_manager,\n            \"get_certificate_info\",\n            side_effect=Exception(\"Manager error\"),\n        ):\n            result = self.cert.get_certificate_details(\"http://test.com/cert/123\")\n            self.assertEqual(result[\"code\"], 500)\n\n    def test_122_get_certificate_details_success(self):\n        \"\"\"Test get_certificate_details success case\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert.certificate_manager,\n            \"get_certificate_info\",\n            return_value={\"order__status_id\": 5, \"cert\": \"cert\"},\n        ), patch.object(\n            self.cert, \"_determine_certificate_response\", return_value={\"code\": 200}\n        ) as mock_determine:\n            result = self.cert.get_certificate_details(\"http://test.com/cert/123\")\n            mock_determine.assert_called()\n\n    def test_123_get_certificate_details_unexpected_error(self):\n        \"\"\"Test get_certificate_details with unexpected error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"Unexpected\")\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.get_certificate_details(\"http://test.com/cert/123\")\n                self.assertEqual(result[\"code\"], 500)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in get_certificate_details: Unexpected\",\n                log.output,\n            )\n\n    def test_124_validate_certificate_request_message_success(self):\n        \"\"\"Test _validate_certificate_request_message success\"\"\"\n        with patch.object(\n            self.cert.message,\n            \"check\",\n            return_value=(200, \"msg\", \"detail\", {}, {}, \"account\"),\n        ):\n            result = self.cert._validate_certificate_request_message(\"content\")\n            self.assertEqual(result[0], 200)\n\n    def test_125_validate_certificate_request_message_error(self):\n        \"\"\"Test _validate_certificate_request_message with error\"\"\"\n        with patch.object(\n            self.cert.message, \"check\", side_effect=Exception(\"Message error\")\n        ):\n            result = self.cert._validate_certificate_request_message(\"content\")\n            self.assertEqual(result[0], 400)\n\n    def test_126_prepare_certificate_response_success(self):\n        \"\"\"Test _prepare_certificate_response success\"\"\"\n        with patch.object(\n            self.cert.message,\n            \"prepare_response\",\n            return_value={\"code\": 200, \"data\": \"response\"},\n        ):\n            result = self.cert._prepare_certificate_response(\n                {}, 200, \"message\", \"detail\"\n            )\n            self.assertEqual(result[\"code\"], 200)\n\n    def test_127_prepare_certificate_response_with_dict_data(self):\n        \"\"\"Test _prepare_certificate_response with dict data\"\"\"\n        with patch.object(\n            self.cert.message,\n            \"prepare_response\",\n            return_value={\"code\": 200, \"data\": {\"key\": \"value\"}},\n        ):\n            result = self.cert._prepare_certificate_response(\n                {}, 200, \"message\", \"detail\"\n            )\n            self.assertIsInstance(result[\"data\"], str)  # Should be JSON string\n\n    def test_128_prepare_certificate_response_error(self):\n        \"\"\"Test _prepare_certificate_response with error\"\"\"\n        with patch.object(\n            self.cert.message,\n            \"prepare_response\",\n            side_effect=Exception(\"Response error\"),\n        ):\n            result = self.cert._prepare_certificate_response(\n                {}, 200, \"message\", \"detail\"\n            )\n            self.assertEqual(result[\"code\"], 500)\n\n    def test_129_process_certificate_request_invalid_content(self):\n        \"\"\"Test process_certificate_request with invalid content\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={\"content\": \"error\"}\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 400}\n        ):\n            result = self.cert.process_certificate_request(\"\")\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_130_process_certificate_request_message_validation_error(self):\n        \"\"\"Test process_certificate_request with message validation error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(400, \"error\", \"detail\", {}, {}, \"\"),\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 400}\n        ):\n            result = self.cert.process_certificate_request(\"content\")\n            self.assertIn(\"code\", result)\n\n    def test_131_process_certificate_request_success_with_url(self):\n        \"\"\"Test process_certificate_request success with URL in protected header\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {\"url\": \"http://test.com\"}, {}, \"\"),\n        ), patch.object(\n            self.cert,\n            \"get_certificate_details\",\n            return_value={\"code\": 200, \"data\": \"cert\"},\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 200}\n        ) as mock_prepare:\n            result = self.cert.process_certificate_request(\"content\")\n            mock_prepare.assert_called()\n\n    def test_132_process_certificate_request_success_with_url(self):\n        \"\"\"Test process_certificate_request success with URL in protected header\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {\"url\": \"http://test.com\"}, {}, \"\"),\n        ), patch.object(\n            self.cert,\n            \"get_certificate_details\",\n            return_value={\"code\": 400, \"data\": \"data\", \"detail\": \"error\"},\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 109}\n        ) as mock_prepare:\n            result = self.cert.process_certificate_request(\"content\")\n            mock_prepare.assert_called_with(\n                {\"code\": 400, \"data\": \"data\", \"detail\": \"error\"}, 400, \"data\", \"error\"\n            )\n\n    def test_133_process_certificate_request_missing_url(self):\n        \"\"\"Test process_certificate_request with missing URL in protected header\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {}, {}, \"\"),\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 400}\n        ) as mock_prepare:\n            result = self.cert.process_certificate_request(\"content\")\n            mock_prepare.assert_called()\n\n    def test_134_process_certificate_request_get_details_error(self):\n        \"\"\"Test process_certificate_request with get_certificate_details error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {\"url\": \"http://test.com\"}, {}, \"\"),\n        ), patch.object(\n            self.cert,\n            \"get_certificate_details\",\n            side_effect=Exception(\"Get details error\"),\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 500}\n        ) as mock_prepare:\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_request(\"content\")\n                self.assertEqual(result[\"code\"], 500)\n            self.assertIn(\n                \"ERROR:test_a2c:Error getting certificate details: Get details error\",\n                log.output,\n            )\n            mock_prepare.assert_called()\n\n    def test_135_process_certificate_request_unexpected_error(self):\n        \"\"\"Test process_certificate_request with unexpected error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"Unexpected\")\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 500}\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_request(\"content\")\n                self.assertEqual(result[\"code\"], 500)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in process_certificate_request: Unexpected\",\n                log.output,\n            )\n\n    def test_136_validate_revocation_message_success(self):\n        \"\"\"Test _validate_revocation_message success\"\"\"\n        with patch.object(\n            self.cert.message,\n            \"check\",\n            return_value=(200, \"msg\", \"detail\", \"protected\", {}, \"account\"),\n        ):\n            result = self.cert._validate_revocation_message(\"content\")\n            self.assertEqual(result[0], 200)\n\n    def test_137_validate_revocation_message_error(self):\n        \"\"\"Test _validate_revocation_message with error\"\"\"\n        with patch.object(\n            self.cert.message, \"check\", side_effect=Exception(\"Message error\")\n        ):\n            result = self.cert._validate_revocation_message(\"content\")\n            self.assertEqual(result[0], 400)\n\n    def test_138_process_certificate_revocation_validation_error(self):\n        \"\"\"Test _process_certificate_revocation with validation error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_revocation_request\", return_value=(400, \"error\")\n        ):\n            result = self.cert._process_certificate_revocation(\"account\", {})\n            self.assertEqual(result, (400, \"error\", None))\n\n    def test_139_process_certificate_revocation_success(self):\n        \"\"\"Test _process_certificate_revocation success\"\"\"\n        payload = {\"certificate\": \"cert\"}\n        with patch.object(\n            self.cert, \"_validate_revocation_request\", return_value=(200, \"unspecified\")\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler:\n            mock_ca = MagicMock()\n            mock_ca.revoke.return_value = (200, \"revoked\", \"detail\")\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n\n            result = self.cert._process_certificate_revocation(\"account\", payload)\n            self.assertEqual(result, (200, \"revoked\", \"detail\"))\n\n    def test_140_process_certificate_revocation_with_logging(self):\n        \"\"\"Test _process_certificate_revocation with operations logging\"\"\"\n        payload = {\"certificate\": \"cert\"}\n        self.cert.config.cert_operations_log = \"json\"\n        with patch.object(\n            self.cert, \"_validate_revocation_request\", return_value=(200, \"unspecified\")\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert.certificate_logger, \"log_certificate_revocation\"\n        ) as mock_log:\n            mock_ca = MagicMock()\n            mock_ca.revoke.return_value = (200, \"revoked\", \"detail\")\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n\n            result = self.cert._process_certificate_revocation(\"account\", payload)\n            mock_log.assert_called_with(\"cert\", 200)\n\n    def test_141_process_certificate_revocation_logging_error(self):\n        \"\"\"Test _process_certificate_revocation with logging error\"\"\"\n        payload = {\"certificate\": \"cert\"}\n        self.cert.config.cert_operations_log = \"json\"\n        with patch.object(\n            self.cert, \"_validate_revocation_request\", return_value=(200, \"unspecified\")\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert.certificate_logger,\n            \"log_certificate_revocation\",\n            side_effect=Exception(\"Log error\"),\n        ):\n            mock_ca = MagicMock()\n            mock_ca.revoke.return_value = (200, \"revoked\", \"detail\")\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n\n            result = self.cert._process_certificate_revocation(\"account\", payload)\n            self.assertEqual(\n                result, (200, \"revoked\", \"detail\")\n            )  # Should still succeed despite log error\n\n    def test_142_process_certificate_revocation_exception(self):\n        \"\"\"Test _process_certificate_revocation with exception\"\"\"\n        with patch.object(\n            self.cert,\n            \"_validate_revocation_request\",\n            side_effect=Exception(\"Revocation error\"),\n        ):\n            result = self.cert._process_certificate_revocation(\"account\", {})\n            self.assertEqual(result[0], 500)\n\n    def test_143_revoke_certificate_invalid_content(self):\n        \"\"\"Test revoke_certificate with invalid content\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={\"content\": \"error\"}\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 400}\n        ):\n            result = self.cert.revoke_certificate(\"\")\n            self.assertIn(\"code\", result)\n\n    def test_144_revoke_certificate_message_validation_error(self):\n        \"\"\"Test revoke_certificate with message validation error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_revocation_message\",\n            return_value=(400, \"error\", \"detail\", \"\", {}, \"\"),\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 400}\n        ):\n            result = self.cert.revoke_certificate(\"content\")\n            self.assertIn(\"code\", result)\n\n    def test_145_revoke_certificate_success(self):\n        \"\"\"Test revoke_certificate success\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_revocation_message\",\n            return_value=(200, \"ok\", \"\", \"\", {\"certificate\": \"cert\"}, \"account\"),\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_revocation\",\n            return_value=(200, \"revoked\", \"detail\"),\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 200}\n        ) as mock_prepare:\n            result = self.cert.revoke_certificate(\"content\")\n            mock_prepare.assert_called()\n\n    def test_146_revoke_certificate_unexpected_error(self):\n        \"\"\"Test revoke_certificate with unexpected error\"\"\"\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"Unexpected\")\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 500}\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.revoke_certificate(\"content\")\n                self.assertIn(\"code\", result)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in revoke_certificate: Unexpected\",\n                log.output,\n            )\n\n    def test_147_process_enrollment_and_store_certificate_success(self):\n        # Pre-enrollment hooks return empty (no error)\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[]\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_enrollment\",\n            return_value=(None, \"cert\", \"raw\", \"poll\", False),\n        ), patch.object(\n            self.cert,\n            \"_store_certificate_and_update_order\",\n            return_value=(\"result\", None),\n        ), patch.object(\n            self.cert.certificate_logger, \"log_certificate_issuance\"\n        ) as mock_log, patch.object(\n            self.cert, \"_execute_post_enrollment_hooks\", return_value=[]\n        ):\n            self.cert.config.cert_operations_log = \"json\"\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, (\"result\", None, None))\n            mock_log.assert_called_with(\"cert_name\", \"raw\", \"order_name\", False)\n\n    def test_148_process_enrollment_and_store_certificate_enrollment_error(self):\n        # Enrollment returns no certificate, triggers error handling\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[]\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_enrollment\",\n            return_value=(\"error\", None, None, \"poll\", False),\n        ), patch.object(\n            self.cert,\n            \"_handle_enrollment_error\",\n            return_value=(\"result\", \"error\", \"detail\"),\n        ) as mock_handle, patch.object(\n            self.cert, \"_execute_post_enrollment_hooks\", return_value=[]\n        ):\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, (\"result\", \"error\", \"detail\"))\n            mock_handle.assert_called()\n\n    def test_149_process_enrollment_and_store_certificate_pre_hook_error(self):\n        # Pre-enrollment hook returns error, should return early\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[\"pre_hook_error\"]\n        ):\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, [\"pre_hook_error\"])\n\n    def test_150_process_enrollment_and_store_certificate_post_hook_error(self):\n        # Post-enrollment hook returns error, should return early\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[]\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_enrollment\",\n            return_value=(None, \"cert\", \"raw\", \"poll\", False),\n        ), patch.object(\n            self.cert,\n            \"_store_certificate_and_update_order\",\n            return_value=(\"result\", None),\n        ), patch.object(\n            self.cert.certificate_logger, \"log_certificate_issuance\"\n        ), patch.object(\n            self.cert,\n            \"_execute_post_enrollment_hooks\",\n            return_value=[\"post_hook_error\"],\n        ):\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, [\"post_hook_error\"])\n\n    def test_151_process_enrollment_and_store_certificate_store_error(self):\n        # _store_certificate_and_update_order returns error, should return error\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[]\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_enrollment\",\n            return_value=(None, \"cert\", \"raw\", \"poll\", False),\n        ), patch.object(\n            self.cert,\n            \"_store_certificate_and_update_order\",\n            return_value=(\"result\", \"error\"),\n        ):\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, \"error\")\n\n    def test_152_process_enrollment_and_store_certificate_logger_exception(self):\n        # Exception in logger should not crash method\n        with patch.object(\n            self.cert, \"_execute_pre_enrollment_hooks\", return_value=[]\n        ), patch.object(\n            self.cert,\n            \"_process_certificate_enrollment\",\n            return_value=(None, \"cert\", \"raw\", \"poll\", False),\n        ), patch.object(\n            self.cert,\n            \"_store_certificate_and_update_order\",\n            return_value=(\"result\", None),\n        ), patch.object(\n            self.cert.certificate_logger,\n            \"log_certificate_issuance\",\n            side_effect=Exception(\"log error\"),\n        ), patch.object(\n            self.cert, \"_execute_post_enrollment_hooks\", return_value=[]\n        ):\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n            self.assertEqual(result, (\"result\", None, None))\n\n    def test_153_get_certificate_info_success(self):\n        # Covers: _get_certificate_info normal DB lookup (line 1106)\n        cert = self.cert\n        cert.repository.certificate_lookup.return_value = {\"foo\": \"bar\"}\n        result = cert._get_certificate_info(\"cert_name\")\n        self.assertEqual(result, {\"foo\": \"bar\"})\n        cert.repository.certificate_lookup.assert_called_with(\n            \"name\", \"cert_name\", (\"name\", \"csr\", \"cert\", \"order__name\")\n        )\n\n    def test_154_get_certificate_info_db_error(self):\n        # Covers: _get_certificate_info exception/critical branch (lines 1118-1119)\n        self.cert.repository.certificate_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as lcm:\n            result = self.cert._get_certificate_info(\"cert_name\")\n            self.assertIsNone(result)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to get certificate info: fail\",\n            lcm.output,\n        )\n\n    def test_155_process_certificate_request_code_200_no_url(self):\n        # Covers: process_certificate_request else branch for missing url in protected\n        with patch(\n            \"acme_srv.certificate.error_dic_get\",\n            return_value={\"serverinternal\": \"err\", \"malformed\": \"malf\"},\n        ):\n            with patch(\"acme_srv.message.Message.__init__\", new=MagicMock()):\n                self.cert.logger = MagicMock()\n                self.cert._validate_input_parameters = MagicMock(return_value=None)\n                self.cert._validate_certificate_request_message = MagicMock(\n                    return_value=(200, \"ok\", \"detail\", {}, {}, \"\")\n                )\n                self.cert._prepare_certificate_response = MagicMock(\n                    return_value={\"code\": 400, \"data\": \"error\"}\n                )\n                result = self.cert.process_certificate_request(\"dummy-content\")\n                self.cert._prepare_certificate_response.assert_called_with(\n                    {}, 400, \"malformed\", \"url missing in protected header\"\n                )\n                self.assertEqual(result, {\"code\": 400, \"data\": \"error\"})\n\n    def test_156_store_certificate_signing_request_unexpected_exception(self):\n        # Covers: store_certificate_signing_request exception branch (lines 1824-1826)\n        with patch(\n            \"acme_srv.certificate.error_dic_get\",\n            return_value={\"serverinternal\": \"err\", \"malformed\": \"malf\"},\n        ):\n            with patch(\"acme_srv.message.Message.__init__\", new=MagicMock()):\n                # Use a real logger mock and ensure it's set on the cert\n                self.cert.certificate_manager = MagicMock()\n                self.cert.certificate_manager.validate_and_store_csr.side_effect = (\n                    Exception(\"unexpected error\")\n                )\n                with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                    result = self.cert.store_certificate_signing_request(\n                        \"order1\", \"csrdata\", \"headerinfo\"\n                    )\n                    # Should return empty string\n                    assert result == \"\", f\"Expected empty string, got {result!r}\"\n                self.assertIn(\n                    \"ERROR:test_a2c:Error during CSR validation and storage: unexpected error\",\n                    log.output,\n                )\n                self.assertIn(\n                    \"ERROR:test_a2c:Failed to store CSR for order order1\",\n                    log.output,\n                )\n\n    def test_157_poll_certificate_status_unexpected_exception(self):\n        # Covers: poll_certificate_status except branch for unexpected exception\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"fail\")\n        ), patch.object(self.cert.logger, \"critical\") as mock_critical:\n            result = self.cert.poll_certificate_status(\"cert\", \"poll\", \"csr\", \"order\")\n            self.assertIsNone(result)\n            mock_critical.assert_called_with(\n                \"Unexpected error in poll_certificate_status: %s\", unittest.mock.ANY\n            )\n\n    def test_158_handle_successful_certificate_poll_order_update_exception(self):\n        # Covers: _handle_successful_certificate_poll except branch for order_update\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", return_value=123\n        ), patch.object(\n            self.cert.repository, \"order_update\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert.logger, \"critical\"\n        ) as mock_critical:\n            result = self.cert._handle_successful_certificate_poll(\n                \"cert\", \"cert\", \"raw\", \"order\"\n            )\n            self.assertEqual(result, 123)\n            mock_critical.assert_called_with(\n                \"Database error updating order status during polling: %s\",\n                unittest.mock.ANY,\n            )\n\n    def test_159_process_certificate_request_get_certificate_details_exception(self):\n        # Covers: process_certificate_request except block for get_certificate_details\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {\"url\": \"http://test.com\"}, {}, \"\"),\n        ), patch.object(\n            self.cert, \"get_certificate_details\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 500}\n        ) as mock_prepare:\n            result = self.cert.process_certificate_request(\"content\")\n            mock_prepare.assert_called()\n            self.assertEqual(result[\"code\"], 500)\n\n    def test_160_process_certificate_request_outer_exception(self):\n        # Covers: process_certificate_request outer except block\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 500}\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.process_certificate_request(\"content\")\n                self.assertEqual(result[\"code\"], 500)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in process_certificate_request: fail\",\n                log.output,\n            )\n\n    def test_161_process_certificate_request_url_missing(self):\n        # Covers: process_certificate_request else branch for missing url in protected\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_certificate_request_message\",\n            return_value=(200, \"ok\", \"\", {}, {}, \"\"),\n        ), patch.object(\n            self.cert, \"_prepare_certificate_response\", return_value={\"code\": 400}\n        ) as mock_prepare:\n            result = self.cert.process_certificate_request(\"content\")\n            mock_prepare.assert_called()\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_162_process_certificate_revocation_logger_warning(self):\n        # Covers: _process_certificate_revocation logger.warning branch\n        payload = {\"certificate\": \"cert\"}\n        self.cert.config.cert_operations_log = \"json\"\n        with patch.object(\n            self.cert, \"_validate_revocation_request\", return_value=(200, \"unspecified\")\n        ), patch.object(self.cert, \"cahandler\") as mock_cahandler, patch.object(\n            self.cert.certificate_logger,\n            \"log_certificate_revocation\",\n            side_effect=Exception(\"fail\"),\n        ):\n            mock_ca = MagicMock()\n            mock_ca.revoke.return_value = (200, \"revoked\", \"detail\")\n            mock_cahandler.return_value.__enter__.return_value = mock_ca\n            with self.assertLogs(self.cert.logger, level=\"WARNING\") as log:\n                result = self.cert._process_certificate_revocation(\"account\", payload)\n                self.assertEqual(result, (200, \"revoked\", \"detail\"))\n            self.assertIn(\n                \"WARNING:test_a2c:Failed to log certificate revocation: fail\",\n                log.output,\n            )\n\n    def test_163_revoke_certificate_payload_missing_certificate(self):\n        # Covers: revoke_certificate branch where payload is missing 'certificate'\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", return_value={}\n        ), patch.object(\n            self.cert,\n            \"_validate_revocation_message\",\n            return_value=(200, \"ok\", \"\", \"\", {}, \"\"),\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 400}\n        ) as mock_prepare:\n            result = self.cert.revoke_certificate(\"content\")\n            mock_prepare.assert_called()\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_164_revoke_certificate_outer_exception(self):\n        # Covers: revoke_certificate outer except block\n        with patch.object(\n            self.cert, \"_validate_input_parameters\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert.message, \"prepare_response\", return_value={\"code\": 500}\n        ):\n            with self.assertLogs(self.cert.logger, level=\"ERROR\") as log:\n                result = self.cert.revoke_certificate(\"content\")\n                self.assertEqual(result[\"code\"], 500)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Unexpected error in revoke_certificate: fail\",\n                log.output,\n            )\n\n    def test_165_store_certificate_signing_request_unexpected_exception(self):\n        # Covers: store_certificate_signing_request unexpected exception/critical\n        # Patch logger.debug after the nested try/except to raise an exception\n        self.cert.certificate_manager.validate_and_store_csr.side_effect = Exception(\n            \"fail\"\n        )\n        with self.assertLogs(self.cert.logger, level=\"ERROR\") as lcm:\n            result = self.cert.store_certificate_signing_request(\n                \"order\", \"csr\", \"header\"\n            )\n            self.assertEqual(\"\", result)\n        self.assertIn(\n            \"ERROR:test_a2c:Error during CSR validation and storage: fail\", lcm.output\n        )\n        self.assertIn(\"ERROR:test_a2c:Failed to store CSR for order order\", lcm.output)\n\n    def test_166_poll_certificate_status_unexpected_exception(self):\n        # Covers: poll_certificate_status exception/critical branch (line 1825)\n        self.cert._validate_input_parameters = MagicMock(side_effect=Exception(\"fail\"))\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as lcm:\n            result = self.cert.poll_certificate_status(\n                \"certname\", \"pollid\", \"csr\", \"order\"\n            )\n            self.assertIsNone(result)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Unexpected error in poll_certificate_status: fail\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.cert_serial_get\", return_value=\"serial\")\n    @patch(\"acme_srv.certificate.cert_aki_get\", return_value=\"aki\")\n    def test_167_store_certificate_in_database_exception(\n        self, mock_aki_get, mock_serial_get\n    ):\n        # Covers: _store_certificate_in_database exception/critical branch\n        self.cert._get_certificate_renewal_info = MagicMock(return_value=\"renewal_info\")\n        self.cert.repository.certificate_add.side_effect = Exception(\"db error\")\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as lcm:\n            result = self.cert._store_certificate_in_database(\n                \"certname\", \"cert\", \"raw\", 1, 2, \"pollid\"\n            )\n            self.assertIsNone(result)\n            self.assertIn(\n                \"CRITICAL:test_a2c:acme2certifier database error in Certificate._store_certificate_in_database(): db error\",\n                lcm.output,\n            )\n\n    def test_168_store_certificate_error_exception(self):\n        # Covers: _store_certificate_error exception/critical branch\n        self.cert.repository.certificate_add.side_effect = Exception(\"fail\")\n\n        with self.assertLogs(self.cert.logger, level=\"CRITICAL\") as lcm:\n            result = self.cert._store_certificate_error(\"cert_name\", \"error\", \"poll_id\")\n            self.assertIsNone(result)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to store certificate error: fail\",\n            lcm.output,\n        )\n\n    def test_169_check_tnauth_identifier_match_true(self):\n        # Covers: _check_tnauth_identifier_match type/value match (lines 1245-1246)\n        cert = self.cert\n        identifier = {\"type\": \"tnauthlist\", \"value\": \"abc\"}\n        tnauthlist = [\"abc\", \"def\"]\n        result = cert._check_tnauth_identifier_match(identifier, tnauthlist)\n        self.assertTrue(result)\n\n    def test_170_check_tnauth_identifier_match_false(self):\n        # Covers: _check_tnauth_identifier_match no match (lines 1245-1246)\n        cert = self.cert\n        identifier = {\"type\": \"tnauthlist\", \"value\": \"xyz\"}\n        tnauthlist = [\"abc\", \"def\"]\n        result = cert._check_tnauth_identifier_match(identifier, tnauthlist)\n        self.assertFalse(result)\n\n    def test_171_check_identifier_match_true(self):\n        # Covers: _check_identifier_match for-loop/if-branch (line 1221)\n        cert = self.cert\n        identifiers = [\n            {\"type\": \"dns\", \"value\": \"foo\"},\n            {\"type\": \"email\", \"value\": \"bar\"},\n        ]\n        result = cert._check_identifier_match(\"dns\", \"foo\", identifiers, False)\n        self.assertTrue(result)\n\n    def test_172_check_identifier_match_false(self):\n        # Covers: _check_identifier_match return (line 1223) when no match\n        cert = self.cert\n        identifiers = [\n            {\"type\": \"dns\", \"value\": \"baz\"},\n            {\"type\": \"email\", \"value\": \"bar\"},\n        ]\n        result = cert._check_identifier_match(\"dns\", \"foo\", identifiers, False)\n        self.assertFalse(result)\n\n    def test_173_validate_revocation_request_unauthorized_forced(self):\n        # Force coverage for line 1171 by using a custom err_msg_dic with a side effect\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n\n        payload = {\"reason\": 0, \"certificate\": \"cert\"}\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=\"order\"\n        ), patch.object(self.cert, \"_validate_order_authorization\", return_value=False):\n            with self.assertLogs(self.cert.logger, level=\"DEBUG\") as log:\n                code, error = self.cert._validate_revocation_request(\"acc\", payload)\n                self.assertEqual(code, 400)\n                self.assertEqual(error, \"unauth\")\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._validate_revocation_request() ended with: 400, unauth\",\n                log.output,\n            )\n\n    def test_174_validate_revocation_request_unauthorized_minimal(self):\n        # Isolated test to guarantee coverage for line 1171 (unauthorized branch)\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        payload = {\"reason\": 0, \"certificate\": \"cert\"}\n        # Patch only the necessary methods\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=\"order\"\n        ), patch.object(self.cert, \"_validate_order_authorization\", return_value=False):\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 400)\n            self.assertEqual(error, \"unauth\")\n\n    def test_175_validate_revocation_request_bad_reason(self):\n        # Covers line 1159: error = self.err_msg_dic[\"badrevocationreason\"]\n        payload = {\"reason\": 99, \"certificate\": \"cert\"}  # 99 is not a valid reason\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        code, error = self.cert._validate_revocation_request(\"acc\", payload)\n        self.assertEqual(code, 400)\n        self.assertEqual(error, \"badreason\")\n\n    def test_176_validate_revocation_request_no_reason(self):\n        # Covers line 1162: rev_reason = \"unspecified\"\n        payload = {\"certificate\": \"cert\"}\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=None\n        ):\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 400)\n            self.assertEqual(error, \"unspecified\")\n\n    def test_177_validate_revocation_request_unauthorized(self):\n        # Explicitly cover line 1171: error = self.err_msg_dic[\"unauthorized\"]\n        payload = {\"reason\": 0, \"certificate\": \"cert\"}\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        # Patch _validate_certificate_account_ownership to return a non-None value (order_name)\n        # Patch _validate_order_authorization to return False (unauthorized)\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=\"order\"\n        ) as mock_own, patch.object(\n            self.cert, \"_validate_order_authorization\", return_value=False\n        ) as mock_auth:\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 400)\n            self.assertEqual(error, \"unauth\")\n            mock_own.assert_called_once_with(\"acc\", \"cert\")\n            mock_auth.assert_called_once_with(\"order\", \"cert\")\n\n    def test_178_validate_revocation_request_success(self):\n        # Covers line 1183: code = 200\n        payload = {\"reason\": 0, \"certificate\": \"cert\"}\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=\"order\"\n        ), patch.object(self.cert, \"_validate_order_authorization\", return_value=True):\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 200)\n            self.assertEqual(error, \"unspecified\")\n\n    def test_179_validate_revocation_request_nocert(self):\n        # Explicitly cover line 1171: error = self.err_msg_dic[\"unauthorized\"] and check logger\n        payload = {\"reason\": 0, \"foo\": \"bar\"}\n        self.cert.err_msg_dic = {\n            \"badrevocationreason\": \"badreason\",\n            \"unauthorized\": \"unauth\",\n            \"serverinternal\": \"internal\",\n        }\n        with patch.object(\n            self.cert, \"_validate_certificate_account_ownership\", return_value=\"order\"\n        ) as mock_own, patch.object(\n            self.cert, \"_validate_order_authorization\", return_value=False\n        ) as mock_auth, patch.object(\n            self.cert.logger, \"debug\"\n        ) as mock_logger_debug:\n            code, error = self.cert._validate_revocation_request(\"acc\", payload)\n            self.assertEqual(code, 400)\n            # self.assertEqual(error, 'unauth')\n            # mock_own.assert_called_once_with('acc', 'cert')\n            # mock_auth.assert_called_once_with('order', 'cert')\n            mock_logger_debug.assert_any_call(\n                \"Certificate._validate_revocation_request(): Revocation request missing 'certificate' field\"\n            )\n\n    def test_180_validate_csr_against_order_order_lookup_exception(self):\n        # Covers exception branch at line 720 in _validate_csr_against_order\n        cert_dic = {\"order\": \"order1\"}\n        with patch.object(\n            self.cert, \"_get_certificate_info\", return_value=cert_dic\n        ), patch.object(\n            self.cert.repository, \"order_lookup\", side_effect=Exception(\"lookup failed\")\n        ), patch.object(\n            self.cert.logger, \"critical\"\n        ) as mock_critical:\n            result = self.cert._validate_csr_against_order(\"cert_name\", \"csr\")\n            mock_critical.assert_called_with(\n                \"Database error in Certificate when checking the CSR identifiers: %s\",\n                unittest.mock.ANY,\n            )\n            self.assertFalse(result)\n\n    def test_181_store_certificate_and_update_order_success_hook(self):\n        # Covers success_hook execution and debug log\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.success_hook.return_value = None\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", return_value=1\n        ), patch.object(self.cert, \"_update_order_status\") as mock_update, patch.object(\n            self.cert.logger, \"debug\"\n        ) as mock_debug:\n            result, error = self.cert._store_certificate_and_update_order(\n                \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n            )\n            self.cert.hooks.success_hook.assert_called_with(\n                \"cert_name\", \"order\", \"csr\", \"cert\", \"raw\", \"poll\"\n            )\n            mock_debug.assert_any_call(\n                \"Certificate._store_certificate_and_update_order: success_hook successful\"\n            )\n            self.assertEqual(result, 1)\n            self.assertIsNone(error)\n\n    def test_182_store_certificate_and_update_order_success_hook_exception(self):\n        # Covers exception in success_hook and error logging\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.success_hook.side_effect = Exception(\"success_hook failed\")\n        self.cert.config.ignore_success_hook_failure = False\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", return_value=1\n        ), patch.object(self.cert, \"_update_order_status\"), patch.object(\n            self.cert.logger, \"error\"\n        ) as mock_logger_error:\n            result, error = self.cert._store_certificate_and_update_order(\n                \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n            )\n            mock_logger_error.assert_called_with(\n                \"Exception during success_hook execution: %s\", unittest.mock.ANY\n            )\n            self.assertEqual(error, (None, \"success_hook_error\", \"success_hook failed\"))\n\n    def test_183_store_certificate_and_update_order_success_hook_exception_ignore(self):\n        # Covers exception in success_hook with ignore_success_hook_failure True (no error returned)\n        self.cert.hooks = MagicMock()\n        self.cert.hooks.success_hook.side_effect = Exception(\"success_hook failed\")\n        self.cert.config.ignore_success_hook_failure = True\n        with patch.object(\n            self.cert, \"_store_certificate_in_database\", return_value=1\n        ), patch.object(self.cert, \"_update_order_status\"), patch.object(\n            self.cert.logger, \"error\"\n        ) as mock_logger_error:\n            result, error = self.cert._store_certificate_and_update_order(\n                \"cert\", \"raw\", \"poll\", \"cert_name\", \"order\", \"csr\"\n            )\n            mock_logger_error.assert_called_with(\n                \"Exception during success_hook execution: %s\", unittest.mock.ANY\n            )\n            self.assertEqual(result, 1)\n            self.assertIsNone(error)\n\n    def test_184_execute_pre_enrollment_hooks_exception(self):\n        # Covers exception branch when pre_hook raises and ignore_pre_hook_failure is False\n        mock_hooks = MagicMock()\n        mock_hooks.pre_hook.side_effect = Exception(\"pre_hook failed\")\n        self.cert.hooks = mock_hooks\n        self.cert.config.ignore_pre_hook_failure = False\n        with patch.object(self.cert.logger, \"error\") as mock_logger_error:\n            result = self.cert._execute_pre_enrollment_hooks(\n                \"cert_name\", \"order_name\", \"csr\"\n            )\n            mock_logger_error.assert_called_with(\n                \"Exception during pre_hook execution: %s\", unittest.mock.ANY\n            )\n            self.assertEqual(result, (None, \"pre_hook_error\", \"pre_hook failed\"))\n\n    def test_185_handle_enrollment_error_no_poll_identifier(self):\n        # Covers branch where poll_identifier is None and error is not special string\n        self.cert.err_msg_dic = {\n            \"serverinternal\": \"serverinternal\",\n            \"rejectedidentifier\": \"rejectedidentifier\",\n        }\n        with patch.object(\n            self.cert, \"_update_order_status\"\n        ) as mock_update, patch.object(\n            self.cert, \"_store_certificate_error\"\n        ) as mock_store:\n            result = self.cert._handle_enrollment_error(\n                \"some_error\", None, \"order1\", \"cert1\"\n            )\n            mock_update.assert_called_with({\"name\": \"order1\", \"status\": \"invalid\"})\n            mock_store.assert_called_with(\"cert1\", \"some_error\", None)\n            self.assertEqual(result, (None, \"serverinternal\", None))\n\n    def test_186_handle_enrollment_error_with_poll_identifier(self):\n        # Covers branch where poll_identifier is set\n        with patch.object(\n            self.cert, \"_update_order_status\"\n        ) as mock_update, patch.object(\n            self.cert, \"_store_certificate_error\"\n        ) as mock_store:\n            result = self.cert._handle_enrollment_error(\n                \"some_error\", \"pollid\", \"order1\", \"cert1\"\n            )\n            # Should not call update_order_status\n            mock_update.assert_not_called()\n            mock_store.assert_called_with(\"cert1\", \"some_error\", \"pollid\")\n            self.assertEqual(result, (None, \"some_error\", \"pollid\"))\n\n    def test_187_handle_enrollment_error_rejected_identifier(self):\n        # Covers branch where error is 'Either CN or SANs are not allowed by configuration'\n        self.cert.err_msg_dic = {\n            \"serverinternal\": \"serverinternal\",\n            \"rejectedidentifier\": \"rejectedidentifier\",\n        }\n        with patch.object(\n            self.cert, \"_update_order_status\"\n        ) as mock_update, patch.object(\n            self.cert, \"_store_certificate_error\"\n        ) as mock_store:\n            result = self.cert._handle_enrollment_error(\n                \"Either CN or SANs are not allowed by configuration\",\n                None,\n                \"order1\",\n                \"cert1\",\n            )\n            mock_update.assert_called_with({\"name\": \"order1\", \"status\": \"invalid\"})\n            mock_store.assert_called_with(\n                \"cert1\", \"Either CN or SANs are not allowed by configuration\", None\n            )\n            self.assertEqual(\n                result,\n                (\n                    None,\n                    \"rejectedidentifier\",\n                    \"CN or SANs are not allowed by configuration\",\n                ),\n            )\n\n    def test_188_handle_enrollment_error_exception(self):\n        # Covers exception branch\n        self.cert.err_msg_dic = {\n            \"serverinternal\": \"serverinternal\",\n            \"rejectedidentifier\": \"rejectedidentifier\",\n        }\n        with patch.object(\n            self.cert, \"_update_order_status\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert, \"_store_certificate_error\", side_effect=Exception(\"fail\")\n        ), patch.object(\n            self.cert.logger, \"critical\"\n        ) as mock_critical:\n            result = self.cert._handle_enrollment_error(\n                \"some_error\", None, \"order1\", \"cert1\"\n            )\n            mock_critical.assert_called()\n            self.assertEqual(result, (None, \"serverinternal\", None))\n\n    def test_189_validate_certificate_authorization_sans_exception(self):\n        # Explicitly covers lines 477-481: exception in cert_san_get triggers warning and returns []\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", side_effect=Exception(\"fail\")\n        ), patch.object(self.cert.logger, \"warning\") as mock_warning, patch.object(\n            self.cert.logger, \"debug\"\n        ) as mock_debug:\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [])\n            mock_warning.assert_called_with(\n                \"Error while parsing certificate for SAN identifier check: %s\",\n                unittest.mock.ANY,\n            )\n            mock_debug.assert_any_call(\n                \"Certificate._validate_certificate_authorization() ended\"\n            )\n\n    def test_190_enter_calls_load_configuration_and_returns_self(self):\n        cert = self.cert\n        with patch.object(cert, \"_load_configuration\") as mock_load_config:\n            result = cert.__enter__()\n            mock_load_config.assert_called_once()\n            self.assertIs(result, cert)\n\n    def test_191_validate_certificate_authorization_cn2san_add(self):\n        # Covers the branch where tnauthlist_support is False and cn2san_add is True\n        self.cert.config.tnauthlist_support = False\n        self.cert.config.cn2san_add = True\n        # Simulate no SANs returned, but CN is present\n        with patch(\"acme_srv.certificate.cert_san_get\", return_value=[]), patch(\n            \"acme_srv.certificate.cert_cn_get\", return_value=\"mycn\"\n        ) as mock_cn_get, patch.object(\n            self.cert, \"_validate_identifiers_against_sans\", return_value=[\"ok\"]\n        ) as mock_validate:\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            # The CN should be added to the SANs list as DNS:mycn\n            mock_cn_get.assert_called_once()\n            mock_validate.assert_called_with([], [\"DNS:mycn\"])\n            self.assertEqual(result, [\"ok\"])\n\n    def test_192_validate_certificate_authorization_sans_exception(self):\n        # Covers lines 477-481: exception in cert_san_get triggers warning and returns []\n        self.cert.config.tnauthlist_support = False\n        with patch(\n            \"acme_srv.certificate.cert_san_get\", side_effect=Exception(\"fail\")\n        ), patch.object(self.cert.logger, \"warning\") as mock_warning, patch.object(\n            self.cert.logger, \"debug\"\n        ) as mock_debug:\n            result = self.cert._validate_certificate_authorization(\n                {\"identifiers\": \"[]\"}, \"cert\"\n            )\n            self.assertEqual(result, [])\n            mock_warning.assert_called_with(\n                \"Error while parsing certificate for SAN identifier check: %s\",\n                unittest.mock.ANY,\n            )\n            mock_debug.assert_any_call(\n                \"Certificate._validate_certificate_authorization() ended\"\n            )\n\n    def test_193_handle_enrollment_thread_execution_async_mode(self):\n        \"\"\"Test _handle_enrollment_thread_execution with async_mode True (lines 1305-1306).\"\"\"\n        self.cert.config.async_mode = True\n        self.cert.config.enrollment_timeout = 5\n        with patch(\"acme_srv.certificate.ThreadWithReturnValue\") as mock_thread:\n            mock_thread_instance = MagicMock()\n            # join should not be called when async_mode is True\n            mock_thread.return_value = mock_thread_instance\n            result = self.cert._handle_enrollment_thread_execution(\n                \"cert_name\", \"csr\", \"order\"\n            )\n            self.assertEqual(result, (None, \"asynchronous enrollment started\"))\n            mock_thread_instance.join.assert_not_called()\n\n    def test_194_check_certificate_reusability_reused_values(self):\n        \"\"\"Test _check_certificate_reusability returns correct cert, cert_raw, and message when reused.\"\"\"\n        cert_data = {\n            \"expire_uts\": 9999999999,\n            \"issue_uts\": 1,\n            \"cert\": \"cert_value\",\n            \"cert_raw\": \"raw_value\",\n            \"created_at\": 1,\n            \"id\": 42,\n        }\n        self.cert.repository.search_certificates.return_value = [cert_data]\n        self.cert.config.cert_reusage_timeframe = 2  # Ensure reuse block is entered\n        with patch(\"acme_srv.certificate.uts_now\", return_value=2):\n            _, cert, cert_raw, message = self.cert._check_certificate_reusability(\"csr\")\n            self.assertEqual(cert, \"cert_value\")\n            self.assertEqual(cert_raw, \"raw_value\")\n            self.assertIn(\"reused certificate from id: 42\", message)\n\n    def test_195_process_enrollment_and_store_certificate_log_exception(self):\n        \"\"\"Test _process_enrollment_and_store_certificate covers log_certificate_issuance exception branch (lines 930-933).\"\"\"\n        self.cert._execute_pre_enrollment_hooks = MagicMock(return_value=[])\n        self.cert._process_certificate_enrollment = MagicMock(\n            return_value=(None, \"cert\", \"raw\", \"poll\", True)\n        )\n        self.cert._store_certificate_and_update_order = MagicMock(\n            return_value=(1, None)\n        )\n        self.cert.config.cert_operations_log = \"json\"\n        self.cert.certificate_logger.log_certificate_issuance = MagicMock(\n            side_effect=Exception(\"log error\")\n        )\n        self.cert._execute_post_enrollment_hooks = MagicMock(return_value=[])\n        # Should not raise, but should call logger.error\n        with self.assertLogs(self.logger, level=\"ERROR\") as log_cm:\n            result = self.cert._process_enrollment_and_store_certificate(\n                \"cert_name\", \"csr\", \"order_name\"\n            )\n        self.assertFalse(result[1])  # No error\n        self.assertIn(\n            \"ERROR:test_a2c:Exception during log_certificate_issuance: log error\",\n            log_cm.output,\n        )\n\n    def test_196_load_configuration_defaults(self):\n        \"\"\"Test _load_configuration uses defaults when config is empty.\"\"\"\n        config = configparser.ConfigParser()\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                self.cert._load_configuration()\n                self.assertEqual(self.cert.config.cert_reusage_timeframe, 0)\n                self.assertEqual(self.cert.config.enrollment_timeout, 5)\n                self.assertEqual(self.cert.config.retry_after, 600)\n                self.assertIsNone(self.cert.config.cert_operations_log)\n                self.assertFalse(self.cert.config.tnauthlist_support)\n                self.assertFalse(self.cert.config.cn2san_add)\n                self.assertFalse(self.cert.config.ignore_pre_hook_failure)\n                self.assertTrue(self.cert.config.ignore_post_hook_failure)\n                self.assertFalse(self.cert.config.ignore_success_hook_failure)\n            self.assertIn(\"CRITICAL:test_a2c:No ca_handler loaded\", lcm.output)\n\n    def test_197_load_configuration_full_config(self):\n        \"\"\"Test _load_configuration with all config sections and values overridden.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Certificate\")\n        config.set(\"Certificate\", \"cert_reusage_timeframe\", \"123\")\n        config.set(\"Certificate\", \"enrollment_timeout\", \"9\")\n        config.set(\"Certificate\", \"retry_after\", \"321\")\n        config.set(\"Certificate\", \"cert_operations_log\", \"JSON\")\n        config.add_section(\"Order\")\n        config.set(\"Order\", \"tnauthlist_support\", \"True\")\n        config.add_section(\"CAhandler\")\n        config.set(\"CAhandler\", \"handler_file\", \"examples/ca_handler/asa_ca_handler.py\")\n        config.add_section(\"Directory\")\n        config.set(\"Directory\", \"url_prefix\", \"/prefix\")\n        config.add_section(\"Hooks\")\n        config.set(\"Hooks\", \"ignore_pre_hook_failure\", \"True\")\n        config.set(\"Hooks\", \"ignore_post_hook_failure\", \"False\")\n        config.set(\"Hooks\", \"ignore_success_hook_failure\", \"True\")\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            self.cert._load_configuration()\n            self.assertEqual(self.cert.config.cert_reusage_timeframe, 123)\n            self.assertEqual(self.cert.config.enrollment_timeout, 9)\n            self.assertEqual(self.cert.config.retry_after, 321)\n            self.assertEqual(self.cert.config.cert_operations_log, \"json\")\n            self.assertTrue(self.cert.config.tnauthlist_support)\n            self.assertTrue(self.cert.config.cn2san_add)\n            self.assertTrue(self.cert.config.ignore_pre_hook_failure)\n            self.assertFalse(self.cert.config.ignore_post_hook_failure)\n            self.assertTrue(self.cert.config.ignore_success_hook_failure)\n\n    def test_198_configuration_partial_config(self):\n        \"\"\"Test _load_configuration with some config sections missing.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Certificate\")\n        config.set(\"Certificate\", \"cert_reusage_timeframe\", \"42\")\n        # No Order, CAhandler, Directory, Hooks\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                self.cert._load_configuration()\n                self.assertEqual(self.cert.config.cert_reusage_timeframe, 42)\n                self.assertEqual(self.cert.config.enrollment_timeout, 5)  # default\n                self.assertEqual(self.cert.config.retry_after, 600)  # default\n                self.assertFalse(self.cert.config.tnauthlist_support)\n                self.assertFalse(self.cert.config.cn2san_add)\n            self.assertIn(\"CRITICAL:test_a2c:No ca_handler loaded\", lcm.output)\n\n    def test_199_configuration_directory_url_prefix(self):\n        \"\"\"Test _load_configuration applies url_prefix to path_dic.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Directory\")\n        config.set(\"Directory\", \"url_prefix\", \"/api\")\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as lcm:\n                self.cert._load_configuration()\n                # path_dic is on the instance, not config\n                self.assertEqual(self.cert.path_dic[\"cert_path\"], \"/api/acme/cert/\")\n            self.assertIn(\"CRITICAL:test_a2c:No ca_handler loaded\", lcm.output)\n\n    def test_200_load_configuration_type_conversion_and_fallback(self):\n        \"\"\"Test _load_configuration handles type conversion and fallback logic.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Certificate\")\n        config.set(\"Certificate\", \"cert_reusage_timeframe\", \"notanint\")\n        config.set(\"Certificate\", \"enrollment_timeout\", \"notanint\")\n        config.set(\"Certificate\", \"retry_after\", \"notanint\")\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n                self.cert._load_configuration()\n                self.assertEqual(self.cert.config.cert_reusage_timeframe, 0)\n                self.assertEqual(self.cert.config.enrollment_timeout, 5)\n                self.assertEqual(self.cert.config.retry_after, 600)\n            self.assertIn(\n                \"ERROR:test_a2c:Invalid cert_reusage_timeframe value in configuration, using default of 0 seconds\",\n                lcm.output,\n            )\n            self.assertIn(\n                \"ERROR:test_a2c:Invalid enrollment_timeout value in configuration, using default of 5 seconds\",\n                lcm.output,\n            )\n            self.assertIn(\n                \"ERROR:test_a2c:Invalid retry_after value in configuration, using default of 600 seconds\",\n                lcm.output,\n            )\n            self.assertIn(\"CRITICAL:test_a2c:No ca_handler loaded\", lcm.output)\n\n    def test_201_load_configuration_logging(self):\n        \"\"\"Test _load_configuration logs debug message.\"\"\"\n        config = configparser.ConfigParser()\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                self.cert._load_configuration()\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_configuration()\", lcm.output\n            )\n\n    def test_202_cert_operations_log_config_applied_to_logger(self):\n        \"\"\"Test that cert_operations_log configuration is applied to CertificateLogger.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Certificate\")\n        config.set(\"Certificate\", \"cert_operations_log\", \"JSON\")\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            # Before loading configuration, CertificateLogger should have None\n            self.assertIsNone(self.cert.certificate_logger.cert_operations_log)\n            # After loading configuration, CertificateLogger should have the config value\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log:\n                self.cert._load_configuration()\n                self.assertEqual(\n                    self.cert.certificate_logger.cert_operations_log, \"json\"\n                )\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_configuration()\", log.output\n            )\n\n    def test_203_cert_operations_log_with_context_manager(self):\n        \"\"\"Test that cert_operations_log is properly applied when using context manager.\"\"\"\n        config = configparser.ConfigParser()\n        config.add_section(\"Certificate\")\n        config.set(\"Certificate\", \"cert_operations_log\", \"TEXT\")\n        with patch(\"acme_srv.certificate.load_config\", return_value=config):\n            # After entering context, config should be loaded and applied\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log:\n                self.cert._load_configuration()\n                self.assertEqual(self.cert.config.cert_operations_log, \"text\")\n                self.assertEqual(\n                    self.cert.certificate_logger.cert_operations_log, \"text\"\n                )\n            self.assertIn(\n                \"DEBUG:test_a2c:Certificate._load_configuration()\", log.output\n            )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_certificate_business_logic.py",
    "content": "import os\nimport unittest\nfrom unittest.mock import MagicMock, patch\nimport sys\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nfrom acme_srv.certificate_business_logic import CertificateBusinessLogic\n\n\nclass TestCertificateBusinessLogic(unittest.TestCase):\n    def setUp(self):\n        self.mock_logger = MagicMock()\n        self.mock_err_msg_dic = {\n            \"badcsr\": \"Invalid CSR\",\n            \"serverinternal\": \"Internal server error\",\n        }\n        self.config = MagicMock()\n        self.config.tnauthlist_support = True\n        self.config.cn2san_add = True\n        self.config.cert_reusage_timeframe = 10\n        self.logic = CertificateBusinessLogic(\n            debug=True,\n            logger=self.mock_logger,\n            err_msg_dic=self.mock_err_msg_dic,\n            config=self.config,\n        )\n\n    @patch(\"acme_srv.certificate_business_logic.csr_load\")\n    def test_001_validate_csr_valid(self, mock_csr_load):\n        mock_csr_load.return_value = MagicMock()\n        code, error, detail = self.logic.validate_csr(\"valid_csr\")\n        self.assertEqual(code, 200)\n        self.assertIsNone(error)\n        self.assertIsNone(detail)\n\n    @patch(\"acme_srv.certificate_business_logic.csr_load\")\n    def test_002_validate_csr_empty(self, mock_csr_load):\n        code, error, detail = self.logic.validate_csr(\"\")\n        self.assertEqual(code, 400)\n        self.assertEqual(error, \"Invalid CSR\")\n        self.assertEqual(detail, \"CSR is empty\")\n\n    @patch(\"acme_srv.certificate_business_logic.csr_load\")\n    def test_003_validate_csr_invalid_format(self, mock_csr_load):\n        mock_csr_load.return_value = None\n        code, error, detail = self.logic.validate_csr(\"bad_csr\")\n        self.assertEqual(code, 400)\n        self.assertEqual(error, \"Invalid CSR\")\n        self.assertEqual(detail, \"CSR format is invalid\")\n\n    @patch(\"acme_srv.certificate_business_logic.csr_load\")\n    def test_004_validate_csr_exception(self, mock_csr_load):\n        mock_csr_load.side_effect = Exception(\"fail\")\n        code, error, detail = self.logic.validate_csr(\"csr\")\n        self.assertEqual(code, 500)\n        self.assertEqual(error, \"Internal server error\")\n        self.assertEqual(detail, \"CSR validation failed\")\n\n    @patch(\"acme_srv.certificate_business_logic.cert_dates_get\")\n    def test_005_calculate_certificate_dates_valid(self, mock_cert_dates_get):\n        mock_cert_dates_get.return_value = (123, 456)\n        issue, expire = self.logic.calculate_certificate_dates(\"cert\")\n        self.assertEqual(issue, 123)\n        self.assertEqual(expire, 456)\n\n    @patch(\"acme_srv.certificate_business_logic.cert_dates_get\")\n    def test_006_calculate_certificate_dates_exception(self, mock_cert_dates_get):\n        mock_cert_dates_get.side_effect = Exception(\"fail\")\n        issue, expire = self.logic.calculate_certificate_dates(\"cert\")\n        self.assertEqual(issue, 0)\n        self.assertEqual(expire, 0)\n\n    @patch(\"acme_srv.certificate_business_logic.generate_random_string\")\n    def test_007_generate_certificate_name(self, mock_generate_random_string):\n        mock_generate_random_string.return_value = \"randomname\"\n        name = self.logic.generate_certificate_name()\n        self.assertEqual(name, \"randomname\")\n\n    def test_008_validate_certificate_data_empty(self):\n        self.assertFalse(self.logic.validate_certificate_data(\"\"))\n\n    def test_009_validate_certificate_data_pem(self):\n        pem = \"-----BEGIN CERTIFICATE-----\\n...-----END CERTIFICATE-----\\n\"\n        self.assertTrue(self.logic.validate_certificate_data(pem))\n\n    def test_010_validate_certificate_data_other(self):\n        self.assertFalse(self.logic.validate_certificate_data(\"something else\"))\n\n    def test_011_validate_certificate_data_exception(self):\n        # Ensure a logger object exists to avoid AttributeError\n        logic = CertificateBusinessLogic(debug=True, logger=MagicMock())\n        # purposely pass an object that could raise internally; method should still return True\n        self.assertFalse(logic.validate_certificate_data(object()))\n\n    @patch(\"acme_srv.certificate_business_logic.cert_serial_get\")\n    @patch(\"acme_srv.certificate_business_logic.cert_cn_get\")\n    @patch(\"acme_srv.certificate_business_logic.cert_san_get\")\n    @patch(\"acme_srv.certificate_business_logic.cert_aki_get\")\n    @patch.object(CertificateBusinessLogic, \"calculate_certificate_dates\")\n    def test_012_extract_certificate_info(\n        self, mock_dates, mock_aki, mock_san, mock_cn, mock_serial\n    ):\n        mock_serial.return_value = \"serial\"\n        mock_cn.return_value = \"cn\"\n        mock_san.return_value = [\"san1\", \"san2\"]\n        mock_aki.return_value = \"aki\"\n        mock_dates.return_value = (111, 222)\n        info = self.logic.extract_certificate_info(\"cert\")\n        self.assertEqual(info[\"serial\"], \"serial\")\n        self.assertEqual(info[\"cn\"], \"cn\")\n        self.assertEqual(info[\"san\"], \"['san1', 'san2']\")\n        self.assertEqual(info[\"aki\"], \"aki\")\n        self.assertEqual(info[\"issue_date\"], 111)\n        self.assertEqual(info[\"expire_date\"], 222)\n\n    @patch(\n        \"acme_srv.certificate_business_logic.cert_serial_get\",\n        side_effect=Exception(\"fail\"),\n    )\n    def test_013_extract_certificate_info_exception(self, mock_serial):\n        info = self.logic.extract_certificate_info(\"cert\")\n        self.assertEqual(info, {})\n\n    @patch(\"acme_srv.certificate_business_logic.string_sanitize\")\n    def test_014_sanitize_certificate_name(self, mock_string_sanitize):\n        mock_string_sanitize.return_value = \"sanitized\"\n        result = self.logic.sanitize_certificate_name(\"name\")\n        self.assertEqual(result, \"sanitized\")\n\n    @patch(\n        \"acme_srv.certificate_business_logic.string_sanitize\",\n        side_effect=Exception(\"fail\"),\n    )\n    def test_015_sanitize_certificate_name_exception(self, mock_string_sanitize):\n        result = self.logic.sanitize_certificate_name(\"name\")\n        self.assertEqual(result, \"name\")\n\n    def test_016_format_certificate_response_with_cert(self):\n        result = self.logic.format_certificate_response(\"cert\", 201)\n        self.assertEqual(result[\"code\"], 201)\n        self.assertEqual(result[\"data\"], \"cert\")\n        self.assertIn(\"headers\", result)\n        self.assertEqual(\n            result[\"headers\"], {\"Content-Type\": \"application/pem-certificate-chain\"}\n        )\n\n    def test_017_format_certificate_response_without_cert(self):\n        result = self.logic.format_certificate_response(\"\", 404)\n        self.assertEqual(result[\"code\"], 404)\n        self.assertEqual(result[\"data\"], \"\")\n        self.assertNotIn(\"headers\", result)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_certificate_manager.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Unit tests for CertificateManager coordination layer\"\"\"\nimport os\nimport unittest\nfrom unittest.mock import MagicMock, Mock, patch\nimport sys\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nfrom acme_srv.certificate_manager import CertificateManager\n\n\nclass TestCertificateManager(unittest.TestCase):\n    def setUp(self):\n\n        self.logger = MagicMock()\n        self.err_msg_dic = {\"serverinternal\": \"serverinternal\"}\n\n        # Repository mock with common methods\n        self.repository = MagicMock()\n\n        # Minimal config stub\n        class Cfg:\n            cert_operations_log = None\n            tnauthlist_support = False\n            cn2san_add = False\n            cert_reusage_timeframe = 0\n\n        self.config = Cfg()\n\n        self.mgr = CertificateManager(\n            debug=True,\n            logger=self.logger,\n            err_msg_dic=self.err_msg_dic,\n            repository=self.repository,\n            config=self.config,\n        )\n\n        # Replace business logic with a mock we control per test\n        self.mgr.business_logic = MagicMock()\n\n    # --- search_certificates ---\n    def test_001_search_certificates_no_cert_field_skips_validation(self):\n        self.repository.search_certificates.return_value = [\n            {\"name\": \"c1\", \"csr\": \"csr1\"}\n        ]\n\n        result = self.mgr.search_certificates(\"name\", \"c1\", [\"name\", \"csr\"])\n\n        self.assertEqual(result[\"count\"], 1)\n        self.assertEqual(result[\"total_found\"], 1)\n        self.mgr.business_logic.validate_certificate_data.assert_not_called()\n\n    def test_002_search_certificates_with_cert_filters_invalid(self):\n        # two certs where first validates True and second False\n        self.repository.search_certificates.return_value = [\n            {\"name\": \"c1\", \"cert\": \"pem1\"},\n            {\"name\": \"c2\", \"cert\": \"pem2\"},\n        ]\n        # Return True for pem1, False for pem2\n        self.mgr.business_logic.validate_certificate_data.side_effect = [True, False]\n\n        result = self.mgr.search_certificates(\"cert\", \"\", [\"name\", \"cert\"])\n\n        self.assertEqual(result[\"count\"], 1)\n        self.assertEqual(result[\"total_found\"], 2)\n        self.assertEqual(result[\"certificates\"], [{\"name\": \"c1\", \"cert\": \"pem1\"}])\n\n    def test_003_search_certificates_repo_returns_none_treated_as_error(self):\n        self.repository.search_certificates.return_value = None\n        result = self.mgr.search_certificates(\"name\", \"x\")\n        self.assertEqual(result[\"count\"], 0)\n        self.assertEqual(result[\"total_found\"], 0)\n        self.assertEqual(result[\"certificates\"], None)\n        self.assertIn(\"error\", result)\n\n    def test_004_search_certificates_repo_raises_exception(self):\n        self.repository.search_certificates.side_effect = RuntimeError(\"boom\")\n        result = self.mgr.search_certificates(\"name\", \"x\")\n        self.assertEqual(result[\"count\"], 0)\n        self.assertEqual(result[\"total_found\"], 0)\n        self.assertEqual(result[\"certificates\"], [])\n        self.assertEqual(result[\"error\"], \"boom\")\n\n    # --- get_certificate_info ---\n    def test_005_get_certificate_info_with_cert_enhances_info(self):\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"clean\"\n        self.repository.get_certificate_info.return_value = {\n            \"name\": \"clean\",\n            \"cert\": \"pem\",\n            \"cert_raw\": \"pemraw\",\n        }\n        self.mgr.business_logic.extract_certificate_info.return_value = {\n            \"serial\": \"01\",\n            \"cn\": \"example.com\",\n        }\n\n        result = self.mgr.get_certificate_info(\" dirty \")\n\n        self.mgr.business_logic.sanitize_certificate_name.assert_called_once()\n        self.mgr.business_logic.extract_certificate_info.assert_called_once_with(\n            \"pemraw\"\n        )\n        self.assertEqual(result[\"serial\"], \"01\")\n        self.assertEqual(result[\"cn\"], \"example.com\")\n\n    def test_006_get_certificate_info_without_cert_no_enhancement(self):\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"clean\"\n        self.repository.get_certificate_info.return_value = {\"name\": \"clean\"}\n\n        result = self.mgr.get_certificate_info(\"name\")\n\n        self.mgr.business_logic.extract_certificate_info.assert_not_called()\n        self.assertEqual(result, {\"name\": \"clean\"})\n\n    # --- store_certificate ---\n    def test_007_store_certificate_only_csr_and_order(self):\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"n\"\n        self.repository.add_certificate.return_value = True\n\n        ok, err = self.mgr.store_certificate(\"n\", csr=\"csr1\", order_name=\"ord1\")\n\n        self.assertTrue(ok)\n        self.assertIsNone(err)\n        self.repository.add_certificate.assert_called_once()\n        stored = self.repository.add_certificate.call_args[0][0]\n        self.assertEqual(stored[\"name\"], \"n\")\n        self.assertEqual(stored[\"csr\"], \"csr1\")\n        self.assertEqual(stored[\"order\"], \"ord1\")\n\n    def test_008_store_certificate_with_header_info(self):\n        # Ensure header_info is stored and logger.debug is called\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"certname\"\n        self.repository.add_certificate.return_value = True\n        header_info = \"header details\"\n        ok, err = self.mgr.store_certificate(\n            \"certname\", csr=\"csr1\", header_info=header_info\n        )\n        self.assertTrue(ok)\n        self.assertIsNone(err)\n        stored = self.repository.add_certificate.call_args[0][0]\n        self.assertEqual(stored[\"header_info\"], header_info)\n        self.logger.debug.assert_any_call(\n            \"CertificateManager.store_certificate(): store header_info with certificate\"\n        )\n\n    def test_009_store_certificate_with_certificate_data_logs_when_enabled(self):\n        # enable operations logging in config\n        self.mgr.cert_operations_log = \"json\"\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"n\"\n        self.mgr.business_logic.calculate_certificate_dates.return_value = (1, 2)\n        self.repository.add_certificate.return_value = True\n\n        ok, err = self.mgr.store_certificate(\n            \"n\", csr=\"csr1\", order_name=\"ord1\", certificate_data=\"pem\"\n        )\n\n        self.assertTrue(ok)\n        self.assertIsNone(err)\n        stored = self.repository.add_certificate.call_args[0][0]\n        self.assertEqual(stored[\"issue_uts\"], 1)\n        self.assertEqual(stored[\"expire_uts\"], 2)\n        self.repository.store_certificate_operation_log.assert_called_once_with(\n            \"n\", \"store\", \"success\"\n        )\n\n    def test_010_store_certificate_failure_paths(self):\n        self.mgr.business_logic.sanitize_certificate_name.return_value = \"n\"\n        self.repository.add_certificate.return_value = False\n\n        ok, err = self.mgr.store_certificate(\"n\", csr=\"csr1\")\n        self.assertFalse(ok)\n        self.assertEqual(err, \"Database storage failed\")\n\n        self.repository.add_certificate.side_effect = RuntimeError(\"dberr\")\n        ok, err = self.mgr.store_certificate(\"n\", csr=\"csr1\")\n        self.assertFalse(ok)\n        self.assertEqual(err, \"dberr\")\n\n    # --- update_certificate_dates ---\n    def test_011_update_certificate_dates_specific_name_success(self):\n        self.repository.get_certificate_info.return_value = {\n            \"name\": \"c1\",\n            \"cert\": \"pem\",\n        }\n        self.mgr.business_logic.calculate_certificate_dates.return_value = (10, 20)\n        self.repository.update_certificate.return_value = True\n\n        updated, errors = self.mgr.update_certificate_dates(\"c1\")\n        self.assertEqual((updated, errors), (1, 0))\n        self.repository.update_certificate.assert_called_once()\n\n    def test_012_update_certificate_dates_list_mixed_results(self):\n        self.repository.search_certificates.return_value = [\n            {\"name\": \"a\", \"cert\": \"pemA\"},\n            {\"name\": \"b\", \"cert\": \"pemB\"},\n            {\"name\": \"c\", \"cert\": None},\n        ]\n        # First update ok, second update fails\n        self.mgr.business_logic.calculate_certificate_dates.side_effect = [\n            (1, 2),\n            (3, 4),\n        ]\n        self.repository.update_certificate.side_effect = [True, False]\n\n        updated, errors = self.mgr.update_certificate_dates()\n        self.assertEqual(updated, 1)\n        self.assertEqual(errors, 1)  # one failed update, one skipped (no cert)\n\n    def test_013_update_certificate_dates_calc_exception_counts_error(self):\n        self.repository.search_certificates.return_value = [\n            {\"name\": \"a\", \"cert\": \"pemA\"}\n        ]\n        self.mgr.business_logic.calculate_certificate_dates.side_effect = RuntimeError(\n            \"calc\"\n        )\n\n        updated, errors = self.mgr.update_certificate_dates()\n        self.assertEqual((updated, errors), (0, 1))\n\n    def test_014_update_certificate_dates_top_level_exception(self):\n        self.repository.search_certificates.side_effect = RuntimeError(\"boom\")\n        updated, errors = self.mgr.update_certificate_dates()\n        self.assertEqual(updated, 0)\n        self.assertEqual(errors, 1)\n\n    def test_015_update_certificate_dates_no_certificates(self):\n        self.repository.search_certificates.return_value = []\n        updated, errors = self.mgr.update_certificate_dates()\n        self.assertEqual((updated, errors), (0, 0))\n\n    # --- cleanup_certificates ---\n\n    def test_016_cleanup_certificates_exception_returns_empty(self):\n        self.repository.search_expired_certificates.side_effect = RuntimeError(\"ops\")\n        fields, report = self.mgr.cleanup_certificates()\n        self.assertEqual((fields, report), ([], []))\n\n    # --- check_account_authorization ---\n    @patch(\"acme_srv.certificate_manager.b64_url_recode\", return_value=\"ENC\")\n    def test_017_check_account_authorization_authorized(self, mock_b64):\n        self.repository.get_account_check_result.return_value = True\n        res = self.mgr.check_account_authorization(\"acc\", \"cert\")\n        self.assertEqual(res[\"status\"], \"authorized\")\n        self.assertEqual(res[\"account\"], \"acc\")\n        self.repository.get_account_check_result.assert_called_once_with(\"acc\", \"ENC\")\n\n    @patch(\"acme_srv.certificate_manager.b64_url_recode\", return_value=\"ENC\")\n    def test_018_check_account_authorization_unauthorized(self, mock_b64):\n        self.repository.get_account_check_result.return_value = False\n        res = self.mgr.check_account_authorization(\"acc\", \"cert\")\n        self.assertEqual(res[\"status\"], \"unauthorized\")\n        self.assertIn(\"error\", res)\n\n    @patch(\"acme_srv.certificate_manager.b64_url_recode\", return_value=\"ENC\")\n    def test_019_check_account_authorization_error(self, mock_b64):\n        self.repository.get_account_check_result.side_effect = RuntimeError(\"db\")\n        res = self.mgr.check_account_authorization(\"acc\", \"cert\")\n        self.assertEqual(res[\"status\"], \"error\")\n        self.assertEqual(res[\"error\"], \"db\")\n\n    # --- prepare_certificate_response ---\n    def test_020_prepare_certificate_response_delegates_to_business_logic(self):\n        self.mgr.business_logic.format_certificate_response.return_value = {\n            \"code\": 200,\n            \"data\": \"pem\",\n        }\n        res = self.mgr.prepare_certificate_response(\"pem\", 200)\n        self.mgr.business_logic.format_certificate_response.assert_called_once_with(\n            \"pem\", 200\n        )\n        self.assertEqual(res[\"code\"], 200)\n        self.assertEqual(res[\"data\"], \"pem\")\n\n    # --- update_order_status ---\n    def test_021_update_order_status_success_with_certificate(self):\n        self.repository.update_order.return_value = True\n        ok = self.mgr.update_order_status(\"o1\", \"valid\", certificate_name=\"c1\")\n        self.assertTrue(ok)\n        self.repository.update_order.assert_called_once_with(\n            {\"name\": \"o1\", \"status\": \"valid\", \"certificate\": \"c1\"}\n        )\n\n    def test_022_update_order_status_failure_on_exception(self):\n        self.repository.update_order.side_effect = RuntimeError(\"db\")\n        ok = self.mgr.update_order_status(\"o1\", \"processing\")\n        self.assertFalse(ok)\n\n    # --- get_certificate_by_order ---\n    def test_023_get_certificate_by_order_enhances_with_info(self):\n        self.repository.get_certificate_by_order.return_value = {\n            \"cert\": \"pem\",\n        }\n        self.mgr.business_logic.extract_certificate_info.return_value = {\"serial\": \"01\"}\n        res = self.mgr.get_certificate_by_order(\"o1\")\n        self.mgr.business_logic.extract_certificate_info.assert_called_once_with(\"pem\")\n        self.assertEqual(res[\"serial\"], \"01\")\n\n    def test_024_get_certificate_by_order_exception_returns_empty(self):\n        self.repository.get_certificate_by_order.side_effect = RuntimeError(\"db\")\n        res = self.mgr.get_certificate_by_order(\"o1\")\n        self.assertEqual(res, {})\n\n    # --- validate_and_store_csr ---\n    def test_025_validate_and_store_csr_validation_fails(self):\n        self.mgr.business_logic.validate_csr.return_value = (400, \"err\", \"detail\")\n        ok, cname = self.mgr.validate_and_store_csr(\"o1\", \"csr\")\n        self.assertFalse(ok)\n        self.assertEqual(cname, \"\")\n\n    def test_026_validate_and_store_csr_stores_and_returns_name(self):\n        self.mgr.business_logic.validate_csr.return_value = (200, None, None)\n        self.mgr.business_logic.generate_certificate_name.return_value = \"cname\"\n        self.mgr.store_certificate = Mock(return_value=(True, None))\n\n        ok, cname = self.mgr.validate_and_store_csr(\"o1\", \"csr\")\n        self.assertTrue(ok)\n        self.assertEqual(cname, \"cname\")\n        self.mgr.store_certificate.assert_called_once_with(\n            \"cname\", \"csr\", \"o1\", header_info=None\n        )\n\n    def test_027_validate_and_store_csr_stores_with_headerinfo_and_returns_name(self):\n        self.mgr.business_logic.validate_csr.return_value = (200, None, None)\n        self.mgr.business_logic.generate_certificate_name.return_value = \"cname\"\n        self.mgr.store_certificate = Mock(return_value=(True, None))\n\n        ok, cname = self.mgr.validate_and_store_csr(\n            \"o1\", \"csr\", header_info=\"headerdata\"\n        )\n        self.assertTrue(ok)\n        self.assertEqual(cname, \"cname\")\n        self.mgr.store_certificate.assert_called_once_with(\n            \"cname\", \"csr\", \"o1\", header_info=\"headerdata\"\n        )\n\n    def test_028_validate_and_store_csr_store_fails_returns_name(self):\n        self.mgr.business_logic.validate_csr.return_value = (200, None, None)\n        self.mgr.business_logic.generate_certificate_name.return_value = \"cname\"\n        self.mgr.store_certificate = Mock(return_value=(False, \"dberr\"))\n\n        ok, cname = self.mgr.validate_and_store_csr(\"o1\", \"csr\")\n        self.assertFalse(ok)\n        self.assertEqual(cname, \"cname\")\n\n    def test_029_validate_and_store_csr_exception_returns_generated_name(self):\n        self.mgr.business_logic.validate_csr.side_effect = RuntimeError(\"oops\")\n        self.mgr.business_logic.generate_certificate_name.return_value = \"cname\"\n\n        ok, cname = self.mgr.validate_and_store_csr(\"o1\", \"csr\")\n        self.assertFalse(ok)\n        self.assertEqual(cname, \"cname\")\n\n    # --- __init__ defaults coverage (no config provided) ---\n    def test_030_init_without_config_uses_defaults(self):\n        repo = MagicMock()\n        mgr = CertificateManager(\n            debug=True,\n            logger=self.logger,\n            err_msg_dic=self.err_msg_dic,\n            repository=repo,\n            config=None,\n        )\n        # When config is None, defaults should be applied\n        self.assertIsNone(mgr.cert_operations_log)\n        self.assertFalse(mgr.tnauthlist_support)\n        # And business_logic should still be constructed\n        self.assertIsNotNone(mgr.business_logic)\n\n    # --- cleanup_certificates() ---\n    def test_031_cleanup_certificates_purge_and_mark(self):\n        from acme_srv.helper import uts_to_date_utc\n\n        # Setup expired certificates\n        certs = [\n            {\n                \"name\": \"cert1\",\n                \"expire_uts\": 100,\n                \"issue_uts\": 50,\n                \"cert\": \"valid\",\n                \"cert_raw\": \"raw1\",\n            },\n            {\n                \"name\": \"cert2\",\n                \"expire_uts\": 0,\n                \"issue_uts\": 50,\n                \"cert\": \"valid\",\n                \"cert_raw\": \"raw2\",\n                \"csr\": \"csr\",\n                \"created_at\": \"2020-01-01T00:00:00Z\",\n            },\n            {\n                \"name\": \"cert3\",\n                \"expire_uts\": 0,\n                \"issue_uts\": 50,\n                \"cert\": \"valid\",\n                \"cert_raw\": None,\n                \"csr\": None,\n            },\n            {\n                \"name\": \"cert4\",\n                \"expire_uts\": 0,\n                \"issue_uts\": 50,\n                \"cert\": \"removed by cleanup\",\n                \"cert_raw\": \"raw4\",\n            },\n        ]\n        self.repository.search_expired_certificates.return_value = certs\n        self.repository.delete_certificate = Mock()\n        self.repository.add_certificate = Mock()\n        # Purge mode\n        _, report = self.mgr.cleanup_certificates(timestamp=200, purge=True)\n        self.assertIn(\"cert1\", report)\n        self.assertIn(\"cert4\", report)\n        self.repository.delete_certificate.assert_any_call(\"cert1\")\n        self.repository.delete_certificate.assert_any_call(\"cert4\")\n        # Mark mode\n        self.repository.delete_certificate.reset_mock()\n        self.repository.add_certificate.reset_mock()\n        ts = 200\n        expected_cert = {\n            \"name\": \"cert1\",\n            \"expire_uts\": 100,\n            \"issue_uts\": 50,\n            \"cert\": f\"removed by certificates.cleanup() on {uts_to_date_utc(ts)}\",\n            \"cert_raw\": \"raw1\",\n        }\n        _, report2 = self.mgr.cleanup_certificates(timestamp=ts, purge=False)\n        self.assertIn(\"cert1\", report2)\n        self.repository.add_certificate.assert_any_call(expected_cert)\n\n    # --- _check_invalidation() ---\n    def test_032_check_invalidation_various_cases(self):\n        # cert with 'removed by' in cert\n        cert = {\"name\": \"c1\", \"cert\": \"removed by cleanup\", \"expire_uts\": 0}\n        self.assertTrue(self.mgr._check_invalidation(cert, 100, purge=True))\n        # cert with expire_uts and not removed\n        cert2 = {\"name\": \"c2\", \"cert\": \"valid\", \"expire_uts\": 0, \"cert_raw\": \"raw\"}\n        with patch.object(self.mgr, \"_get_expiredate\", return_value=True) as m:\n            self.assertTrue(self.mgr._check_invalidation(cert2, 100, purge=False))\n            m.assert_called_once()\n        # cert with no expire_uts\n        cert3 = {\"name\": \"c3\", \"cert\": \"valid\"}\n        self.assertFalse(self.mgr._check_invalidation(cert3, 100, purge=False))\n        # cert with no name\n        cert4 = {\"cert\": \"valid\"}\n        self.assertTrue(self.mgr._check_invalidation(cert4, 100, purge=False))\n\n    # --- _assume_expirydate() ---\n    def test_033_assume_expirydate_various_cases(self):\n        # CSR present, created_at older than 2 weeks\n        cert = {\"csr\": \"csr\", \"created_at\": \"1970-01-01T00:00:00Z\"}\n        # timestamp = 200, so timestamp - (14*86400) = -1209600\n        # Only values between 0 and -1209600 will set to_be_cleared True, which is impossible\n        # So, test with a timestamp that makes the window positive\n        # Let's use timestamp = 1210000, so window is 1210000 - 1209600 = 400\n        # created_at_uts = 100, so 0 < 100 < 400 is True\n        with patch(\"acme_srv.certificate_manager.date_to_uts_utc\", return_value=100):\n            self.assertTrue(self.mgr._assume_expirydate(cert, 1210000, False))\n        # created_at_uts = 500, so 0 < 500 < 400 is False\n        with patch(\"acme_srv.certificate_manager.date_to_uts_utc\", return_value=500):\n            self.assertFalse(self.mgr._assume_expirydate(cert, 1210000, False))\n        # No CSR, no cert\n        cert2 = {\"csr\": None}\n        self.assertTrue(self.mgr._assume_expirydate(cert2, 200, False))\n\n    # --- _get_expiredate() ---\n    def test_034_get_expiredate_various_cases(self):\n        # expire_uts == 0, cert_raw present, expire_uts < timestamp\n        cert = {\"expire_uts\": 0, \"cert_raw\": \"raw\"}\n        with patch(\n            \"acme_srv.certificate_manager.cert_dates_get\", return_value=(10, 50)\n        ):\n            self.assertTrue(self.mgr._get_expiredate(cert, 100, False))\n            self.assertEqual(cert[\"issue_uts\"], 10)\n            self.assertEqual(cert[\"expire_uts\"], 50)\n        # expire_uts == 0, cert_raw missing, fallback to _assume_expirydate\n        cert2 = {\"expire_uts\": 0}\n        with patch.object(self.mgr, \"_assume_expirydate\", return_value=True) as m:\n            self.assertTrue(self.mgr._get_expiredate(cert2, 100, False))\n            m.assert_called_once()\n        # expire_uts != 0\n        cert3 = {\"expire_uts\": 10}\n        self.assertTrue(self.mgr._get_expiredate(cert3, 100, False))\n\n    def test_035_assume_expirydate_csr_present_but_no_created_at(self):\n        # Covers the branch where 'csr' is present but 'created_at' is missing\n        cert = {\"csr\": \"csr\"}\n        # to_be_cleared should remain False\n        self.assertFalse(self.mgr._assume_expirydate(cert, 200, False))\n\n    def test_036_cleanup_certificates_repository_exception(self):\n        # Covers the exception branch when repository.search_expired_certificates raises\n        self.repository.search_expired_certificates.side_effect = RuntimeError(\"fail\")\n        fields, report = self.mgr.cleanup_certificates(timestamp=123, purge=False)\n        self.assertEqual(fields, [])\n        self.assertEqual(report, [])\n\n    def test_037_cleanup_certificates_loop_body_exception(self):\n        # Covers the exception branch inside the for-loop\n        # The first cert will cause an exception in _check_invalidation\n        class DummyRepo:\n            def search_expired_certificates(self, timestamp, field_list):\n                return [{\"name\": \"badcert\"}]\n\n        mgr = CertificateManager(\n            debug=True,\n            logger=self.logger,\n            err_msg_dic=self.err_msg_dic,\n            repository=DummyRepo(),\n            config=self.config,\n        )\n        # Patch _check_invalidation to raise\n        mgr._check_invalidation = Mock(side_effect=RuntimeError(\"badcert\"))\n        fields, report = mgr.cleanup_certificates(timestamp=123, purge=False)\n        self.assertIn(\"name\", fields)\n        self.assertEqual(report, [])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_certificate_repository.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Unit tests for DatabaseCertificateRepository abstraction over DBstore\"\"\"\n\nimport os\nimport unittest\nfrom unittest.mock import MagicMock, patch\nimport sys\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestCertificateRepository(unittest.TestCase):\n    def setUp(self):\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = MagicMock()\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        from acme_srv.certificate_repository import DatabaseCertificateRepository\n\n        self.logger = MagicMock()\n        self.db = MagicMock()\n        self.repo = DatabaseCertificateRepository(self.db, self.logger)\n\n    # --- search_certificates ---\n    def test_001_search_no_vlist_success(self):\n        self.db.certificates_search.return_value = [{\"name\": \"c1\"}]\n        res = self.repo.search_certificates(\"name\", \"c1\")\n        self.db.certificates_search.assert_called_once_with(\"name\", \"c1\")\n        self.assertEqual(res, [{\"name\": \"c1\"}])\n\n    def test_002_search_with_vlist_success(self):\n        self.db.certificates_search.return_value = []\n        res = self.repo.search_certificates(\"name\", \"c1\", [\"name\", \"csr\"])\n        self.db.certificates_search.assert_called_once_with(\n            \"name\", \"c1\", [\"name\", \"csr\"]\n        )\n        self.assertEqual(res, [])\n\n    def test_003_search_exception_returns_none(self):\n        self.db.certificates_search.side_effect = RuntimeError(\"db\")\n        res = self.repo.search_certificates(\"name\", \"c1\")\n        self.assertIsNone(res)\n        self.logger.critical.assert_called()\n\n    # --- get_certificate_info ---\n    def test_004_get_certificate_info_success(self):\n        self.db.certificate_lookup.return_value = {\"name\": \"c1\", \"cert\": \"pem\"}\n        res = self.repo.get_certificate_info(\"c1\")\n        self.db.certificate_lookup.assert_called_once()\n        self.assertEqual(res[\"name\"], \"c1\")\n\n    def test_005_get_certificate_info_exception_returns_empty(self):\n        self.db.certificate_lookup.side_effect = RuntimeError(\"db\")\n        res = self.repo.get_certificate_info(\"c1\")\n        self.assertEqual(res, {})\n\n    def test_006_get_certificate_info_none_from_db_passes_through(self):\n        self.db.certificate_lookup.side_effect = None\n        self.db.certificate_lookup.return_value = None\n        res = self.repo.get_certificate_info(\"c1\")\n        self.assertIsNone(res)\n\n    # --- add / update / delete (name variant) ---\n    def test_007_add_certificate_success(self):\n        self.db.certificate_add.return_value = True\n        ok = self.repo.add_certificate({\"name\": \"c1\"})\n        self.assertTrue(ok)\n\n    def test_008_add_certificate_exception_returns_false(self):\n        self.db.certificate_add.side_effect = RuntimeError(\"db\")\n        ok = self.repo.add_certificate({\"name\": \"c1\"})\n        self.assertFalse(ok)\n\n    def test_009_delete_certificate_success(self):\n        self.db.certificate_delete.return_value = True\n        ok = self.repo.delete_certificate(\"c1\")\n        self.assertTrue(ok)\n\n    def test_010_delete_certificate_exception_returns_false(self):\n        self.db.certificate_delete.side_effect = RuntimeError(\"db\")\n        ok = self.repo.delete_certificate(\"c1\")\n        self.assertFalse(ok)\n\n    # --- account check / update order (middle methods) ---\n    def test_011_get_account_check_result_success(self):\n        self.db.certificate_account_check.return_value = {\"ok\": True}\n        res = self.repo.get_account_check_result(\"acc\", \"cert\")\n        self.assertEqual(res, {\"ok\": True})\n\n    def test_012_get_account_check_result_exception_returns_none(self):\n        self.db.certificate_account_check.side_effect = RuntimeError(\"db\")\n        res = self.repo.get_account_check_result(\"acc\", \"cert\")\n        self.assertIsNone(res)\n\n    def test_013_update_order_success_true(self):\n        self.db.order_update.return_value = (\n            None  # method has no return, repo returns True\n        )\n        ok = self.repo.update_order({\"name\": \"o1\", \"status\": \"valid\"})\n        self.assertTrue(ok)\n\n    def test_014_update_order_exception_returns_false(self):\n        self.db.order_update.side_effect = RuntimeError(\"db\")\n        ok = self.repo.update_order({\"name\": \"o1\", \"status\": \"valid\"})\n        self.assertFalse(ok)\n\n    # --- get_orders_by_account ---\n    def test_015_get_orders_by_account_success_list(self):\n        self.db.orders_search.return_value = [{\"name\": \"o1\"}]\n        res = self.repo.get_orders_by_account(\"acc\")\n        self.assertEqual(res, [{\"name\": \"o1\"}])\n\n    def test_016_get_orders_by_account_empty_to_list(self):\n        self.db.orders_search.return_value = None\n        res = self.repo.get_orders_by_account(\"acc\")\n        self.assertEqual(res, [])\n\n    def test_017_get_orders_by_account_exception_returns_empty(self):\n        self.db.orders_search.side_effect = RuntimeError(\"db\")\n        res = self.repo.get_orders_by_account(\"acc\")\n        self.assertEqual(res, [])\n\n    # --- get_certificate_by_order ---\n    def test_018_get_certificate_by_order_success(self):\n        self.db.certificate_lookup.return_value = {\"name\": \"c1\"}\n        res = self.repo.get_certificate_by_order(\"o1\")\n        self.assertEqual(res, {\"name\": \"c1\"})\n\n    def test_019_get_certificate_by_order_exception_returns_empty(self):\n        self.db.certificate_lookup.side_effect = RuntimeError(\"db\")\n        res = self.repo.get_certificate_by_order(\"o1\")\n        self.assertEqual(res, {})\n\n    # --- store_certificate_operation_log ---\n    def test_020_store_certificate_operation_log_success(self):\n        self.db.cahandler_add.return_value = True\n        ok = self.repo.store_certificate_operation_log(\"c1\", \"store\", \"success\")\n        self.assertTrue(ok)\n        self.db.cahandler_add.assert_called_once()\n\n    def test_021_store_certificate_operation_log_exception_returns_false(self):\n        self.db.cahandler_add.side_effect = RuntimeError(\"db\")\n        ok = self.repo.store_certificate_operation_log(\"c1\", \"store\", \"success\")\n        self.assertFalse(ok)\n\n    # --- bottom API (compatibility) ---\n    def test_022_certificate_account_check_success(self):\n        self.db.certificate_account_check.return_value = True\n        res = self.repo.certificate_account_check(\"acc\", \"cert\")\n        self.assertTrue(res)\n\n    def test_023_certificate_account_check_exception_returns_none(self):\n        self.db.certificate_account_check.side_effect = RuntimeError(\"db\")\n        res = self.repo.certificate_account_check(\"acc\", \"cert\")\n        self.assertIsNone(res)\n\n    def test_024_certificate_lookup_with_vlist_success(self):\n        self.db.certificate_lookup.return_value = {\"name\": \"c1\"}\n        res = self.repo.certificate_lookup(\"name\", \"c1\", [\"name\"])\n        self.db.certificate_lookup.assert_called_once_with(\"name\", \"c1\", [\"name\"])\n        self.assertEqual(res, {\"name\": \"c1\"})\n\n    def test_025_certificate_lookup_without_vlist_success(self):\n        self.db.certificate_lookup.reset_mock()\n        self.db.certificate_lookup.return_value = {\"name\": \"c2\"}\n        res = self.repo.certificate_lookup(\"name\", \"c2\")\n        self.db.certificate_lookup.assert_called_once_with(\"name\", \"c2\")\n        self.assertEqual(res, {\"name\": \"c2\"})\n\n    def test_026_certificate_lookup_exception_returns_empty(self):\n        self.db.certificate_lookup.side_effect = RuntimeError(\"db\")\n        res = self.repo.certificate_lookup(\"name\", \"c1\")\n        self.assertEqual(res, {})\n\n    def test_027_certificate_add_success_returns_id(self):\n        self.db.certificate_add.return_value = 123\n        res = self.repo.certificate_add({\"name\": \"c1\"})\n        self.assertEqual(res, 123)\n\n    def test_028_certificate_add_exception_returns_none(self):\n        self.db.certificate_add.side_effect = RuntimeError(\"db\")\n        res = self.repo.certificate_add({\"name\": \"c1\"})\n        self.assertIsNone(res)\n\n    def test_029_certificate_delete_success(self):\n        self.db.certificate_delete.return_value = True\n        ok = self.repo.certificate_delete(\"name\", \"c1\")\n        self.assertTrue(ok)\n\n    def test_030_certificate_delete_exception_returns_false(self):\n        self.db.certificate_delete.side_effect = RuntimeError(\"db\")\n        ok = self.repo.certificate_delete(\"name\", \"c1\")\n        self.assertFalse(ok)\n\n    def test_031_order_lookup_with_vlist_success(self):\n        self.db.order_lookup.return_value = {\"name\": \"o1\"}\n        res = self.repo.order_lookup(\"name\", \"o1\", [\"name\"])\n        self.db.order_lookup.assert_called_once_with(\"name\", \"o1\", [\"name\"])\n        self.assertEqual(res, {\"name\": \"o1\"})\n\n    def test_032_order_lookup_without_vlist_success(self):\n        self.db.order_lookup.reset_mock()\n        self.db.order_lookup.return_value = {\"name\": \"o2\"}\n        res = self.repo.order_lookup(\"name\", \"o2\")\n        self.db.order_lookup.assert_called_once_with(\"name\", \"o2\")\n        self.assertEqual(res, {\"name\": \"o2\"})\n\n    def test_033_order_lookup_exception_returns_empty(self):\n        self.db.order_lookup.side_effect = RuntimeError(\"db\")\n        res = self.repo.order_lookup(\"name\", \"o1\")\n        self.assertEqual(res, {})\n\n    def test_034_order_update_success(self):\n        self.db.order_update.return_value = True\n        ok = self.repo.order_update({\"name\": \"o1\"})\n        self.assertTrue(ok)\n\n    def test_035_order_update_exception_returns_false(self):\n        self.db.order_update.side_effect = RuntimeError(\"db\")\n        ok = self.repo.order_update({\"name\": \"o1\"})\n        self.assertFalse(ok)\n\n    def test_036_search_expired_certificates_returns_results(self):\n        # Simulate dbstore returning a list of expired certificates\n        certs = [\n            {\"name\": \"expired1\", \"expire_uts\": 100, \"cert\": \"pem1\"},\n            {\"name\": \"expired2\", \"expire_uts\": 200, \"cert\": \"pem2\"},\n        ]\n        self.db.certificates_search.return_value = certs\n        result = self.repo.search_expired_certificates(\n            123456, [\"name\", \"expire_uts\", \"cert\"]\n        )\n        self.db.certificates_search.assert_called_once_with(\n            \"expire_uts\", 123456, [\"name\", \"expire_uts\", \"cert\"], \"<=\"\n        )\n        self.assertEqual(result, certs)\n\n    def test_037_search_expired_certificates_returns_empty(self):\n        # Simulate dbstore returning an empty list\n        self.db.certificates_search.return_value = []\n        result = self.repo.search_expired_certificates(\n            123456, [\"name\", \"expire_uts\", \"cert\"]\n        )\n        self.db.certificates_search.assert_called_once()\n        self.assertEqual(result, [])\n\n    def test_038_search_expired_certificates_raises_exception(self):\n        # Simulate dbstore raising an exception\n        self.db.certificates_search.side_effect = RuntimeError(\"db error\")\n        result = self.repo.search_expired_certificates(\n            123456, [\"name\", \"expire_uts\", \"cert\"]\n        )\n        self.assertEqual(result, [])\n        self.logger.critical.assert_called()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_certifier_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, Mock, MagicMock\nimport requests\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for certifier_ca_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.ca_handler.certifier_ca_handler import CAhandler\n\n        self.cahandler = CAhandler(False, self.logger)\n        # self.cahandler.api_host = 'api_host'\n        # self.cahandler.auth = 'auth'\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"requests.get\")\n    def test_002_ca_get(self, mock_get):\n        \"\"\"CAhandler.get_ca() returns an http error\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_get.side_effect = requests.exceptions.HTTPError\n        self.assertEqual(\n            {\"status\": 500, \"message\": \"\", \"statusMessage\": \"Internal Server Error\"},\n            self.cahandler._ca_get(\"foo\", \"bar\"),\n        )\n\n    @patch(\"requests.get\")\n    def test_003_ca_get(self, mock_get):\n        \"\"\"CAhandler.get_ca() returns no json file\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_get.status_code = 200\n        mock_get.return_value.json = {\"bbs\": \"hahha\"}\n        self.assertEqual(\n            {\n                \"status\": 500,\n                \"message\": \"'dict' object is not callable\",\n                \"statusMessage\": \"Internal Server Error\",\n            },\n            self.cahandler._ca_get(\"foo\", \"bar\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no api_host parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"api_host\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no api_user parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_host\": \"api_host\", \"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertFalse(self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"api_user\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_007_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no api_password parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"foo\": \"bar\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertFalse(self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"api_password\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_008_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no ca_name parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"foo\": \"bar\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"ca_name\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_009_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load standard polling interval\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_010_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load custom polling interval\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n            \"polling_timeout\": 120,\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(120, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_011_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load custom polling interval\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n            \"polling_timeout\": \"aa\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"WARNING:test_a2c:Invalid value for polling_timeout in configuration. Using default: 60\",\n            lcm.output,\n        )\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(60, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_012_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load ca_handler True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n            \"polling_timeout\": 120,\n            \"ca_bundle\": True,\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(120, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_013_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load ca_handler False\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n            \"polling_timeout\": 120,\n            \"ca_bundle\": False,\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(120, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load ca_handler configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_host\": \"api_host\",\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n            \"ca_name\": \"ca_name\",\n            \"foo\": \"bar\",\n            \"polling_timeout\": 120,\n            \"ca_bundle\": \"foo\",\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertEqual(\"foo\", self.cahandler.ca_bundle)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertEqual(120, self.cahandler.polling_timeout)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_user_variable\": \"api_user_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"user_var\", self.cahandler.api_user)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_user_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_user)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load user_variable:'does_not_exist'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_user_variable\": \"api_user_var\",\n            \"api_user\": \"api_user\",\n        }\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual(\"api_user\", self.cahandler.api_user)\n        # self.assertIn(\"foo\", lcm.output)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with password variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_password_variable\": \"api_password_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"password_var\", self.cahandler.api_password)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with password variable which does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_password_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_password)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load passphrase_variable:'does_not_exist'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template override password variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_password_variable\": \"api_password_var\",\n            \"api_password\": \"api_password\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"api_password\", self.cahandler.api_password)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite api_password_variable\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load_cfg, mock_json, mock_url):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"foo\": \"bar\"}\n        mock_json.return_value = \"foo\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load_cfg, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"host\": \"bar:8888\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_chk.called)\n        self.assertEqual(\n            {\"http\": \"proxy.bar.local\", \"https\": \"proxy.bar.local\"},\n            self.cahandler.proxy,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load_cfg, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"host\": \"bar\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_chk.called)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse proxy_server_list from configuration: not enough values to unpack (expected 2, got 1)\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with timeout variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 10}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_025_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with timeout variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"WARNING:test_a2c:Invalid value for request_timeout in configuration. Using default: 20\",\n            lcm.output,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_026_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with timeout variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.profile_id)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.certifier_ca_handler.load_config\")\n    def test_027_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"profile_id\": \"profile_id\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"profile_id\", self.cahandler.profile_id)\n\n    def test_028_auth_set(self):\n        \"\"\"test _auth_set\"\"\"\n        self.cahandler.api_user = \"api_user\"\n        self.cahandler.api_password = \"api_password\"\n        self.cahandler._auth_set()\n        self.assertTrue(self.cahandler.auth)\n\n    def test_029_auth_set(self):\n        \"\"\"test _auth_set without api_user\"\"\"\n        self.cahandler.api_user = None\n        self.cahandler.api_password = \"api_password\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._auth_set()\n        self.assertFalse(self.cahandler.auth)\n        self.assertIn(\n            'ERROR:test_a2c:Auth information incomplete. Either \"api_user\" or \"api_password\" parameter is missing in config file',\n            lcm.output,\n        )\n\n    def test_030_auth_set(self):\n        \"\"\"test _auth_set without api_user\"\"\"\n        self.cahandler.api_user = \"api_user\"\n        self.cahandler.api_password = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._auth_set()\n        self.assertFalse(self.cahandler.auth)\n        self.assertIn(\n            'ERROR:test_a2c:Auth information incomplete. Either \"api_user\" or \"api_password\" parameter is missing in config file',\n            lcm.output,\n        )\n\n    @patch.object(requests, \"post\")\n    def test_031__api_post(self, mock_req):\n        \"\"\"test _api_post successful run\"\"\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._api_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_032__api_post(self, mock_post):\n        \"\"\"CAhandler.get_ca() returns an http error\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_post.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"exc_api_post\", self.cahandler._api_post(\"url\", \"data\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API post() request returned an error: exc_api_post\",\n            lcm.output,\n        )\n\n    @patch.object(requests, \"get\")\n    def test_033__ca_get(self, mock_req):\n        \"\"\"test _ca_get successful run\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._ca_get())\n\n    def test_034__api_post(self):\n        \"\"\"test _ca_get no api_host\"\"\"\n        self.cahandler.auth = \"auth\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual({}, self.cahandler._ca_get())\n        self.assertIn(\n            \"ERROR:test_a2c:api_host parameter is misisng in configuration\",\n            lcm.output,\n        )\n\n    @patch.object(requests, \"get\")\n    def test_035__ca_get(self, mock_req):\n        \"\"\"test _ca_get auth none\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._ca_get())\n\n    @patch(\"requests.get\")\n    def test_036__api_post(self, mock_get):\n        \"\"\"CAhandler.get_ca() returns an http error\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_get.side_effect = Exception(\"exc_ca_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                {\n                    \"status\": 500,\n                    \"message\": \"exc_ca_get\",\n                    \"statusMessage\": \"Internal Server Error\",\n                },\n                self.cahandler._ca_get(),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:API get() request returned error: exc_ca_get\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_037__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns nothing\"\"\"\n        mock_caget.return_value = []\n        self.assertEqual(\n            {\"status\": 404, \"message\": \"CA not found\", \"statusMessage\": \"Not Found\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_038__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns wrong information\"\"\"\n        mock_caget.return_value = \"foo\"\n        self.assertEqual(\n            {\"status\": 404, \"message\": \"CA not found\", \"statusMessage\": \"Not Found\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_039__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns error message\"\"\"\n        mock_caget.return_value = {\"status\": \"status\", \"message\": \"message\"}\n        self.assertEqual(\n            {\"message\": \"message\", \"status\": \"status\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_040__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns empty ca_list\"\"\"\n        mock_caget.return_value = {\"cas\": None}\n        self.assertEqual(\n            {\"status\": 404, \"message\": \"CA not found\", \"statusMessage\": \"Not Found\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_041__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns ca_list but filter does not match\"\"\"\n        mock_caget.return_value = {\"cas\": [{\"foo\": \"bar\"}]}\n        self.assertEqual(\n            {\"status\": 404, \"message\": \"CA not found\", \"statusMessage\": \"Not Found\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_042__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns ca_list but filter matches\"\"\"\n        mock_caget.return_value = {\n            \"cas\": [{\"foo\": \"bar\"}, {\"filterkey\": \"filtervalue\"}, {\"foo1\": \"bar1\"}]\n        }\n        self.assertEqual(\n            {\"filterkey\": \"filtervalue\"},\n            self.cahandler._ca_get_properties(\"filterkey\", \"filtervalue\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_043__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns ca_list another filterkey\"\"\"\n        mock_caget.return_value = {\n            \"cas\": [{\"foo\": \"bar\"}, {\"filterkey\": \"filtervalue\"}, {\"foo1\": \"bar1\"}]\n        }\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._ca_get_properties(\"foo\", \"bar\")\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get\")\n    def test_044__ca_get_properties(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() ca_get returns ca_list filterkey check first match\"\"\"\n        mock_caget.return_value = {\n            \"cas\": [\n                {\"foo\": \"bar_bogus\"},\n                {\"foo\": \"bar\"},\n                {\"foo\": \"bar1\"},\n                {\"foo\": \"bar2\"},\n            ]\n        }\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._ca_get_properties(\"foo\", \"bar\")\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_045__cert_get(self, mock_caget):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties returns empty dic\"\"\"\n        mock_caget.return_value = {}\n        self.assertEqual({}, self.cahandler._cert_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_046__cert_get(self, mock_caget, mock_post):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties does returns \"href\" key\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {\"mock\": \"post\"}\n        self.assertEqual({\"mock\": \"post\"}, self.cahandler._cert_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_047__cert_get(self, mock_caget, mock_post):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties returns \"href\" key but cert_dic is empty\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {}\n        self.assertEqual({\"href\": \"href\"}, self.cahandler._cert_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_048__cert_get(self, mock_caget, mock_post):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties does returns \"href\" key\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.profile_id = 100\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {\"mock\": \"post\"}\n        self.assertEqual({\"mock\": \"post\"}, self.cahandler._cert_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_049__cert_get(self, mock_caget, mock_post):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties does returns \"href\" key\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.profile_id = 100\n        self.cahandler.header_info_field = \"header_info_field\"\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {\"mock\": \"post\"}\n        self.assertEqual({\"mock\": \"post\"}, self.cahandler._cert_get(\"csr\"))\n        self.assertEqual(100, self.cahandler.profile_id)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_050__cert_get(self, mock_caget, mock_post, mock_ecl):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties does returns \"href\" key\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {\"mock\": \"post\"}\n        self.assertEqual({\"mock\": \"post\"}, self.cahandler._cert_get(\"csr\"))\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_051__cert_get(self, mock_caget, mock_post, mock_ecl):\n        \"\"\"CAhandler._ca_get_properties() _ca_get_properties does returns \"href\" key\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.enrollment_config_log = True\n        mock_caget.return_value = {\"href\": \"href\"}\n        mock_post.return_value = {\"mock\": \"post\"}\n        self.assertEqual({\"mock\": \"post\"}, self.cahandler._cert_get(\"csr\"))\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"requests.get\")\n    def test_052__cert_get_properties(self, mock_req):\n        \"\"\"CAhandler._cert_get_properties() all good\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._cert_get_properties(\"serial\", \"link\")\n        )\n\n    @patch(\"requests.get\")\n    def test_053__cert_get_properties(self, mock_get):\n        \"\"\"CAhandler._cert_get_properties() all good\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_get.side_effect = Exception(\"exc_api_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                {\n                    \"status\": 500,\n                    \"message\": \"exc_api_get\",\n                    \"statusMessage\": \"Internal Server Error\",\n                },\n                self.cahandler._cert_get_properties(\"serial\", \"link\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get certificate properties. Error: exc_api_get\",\n            lcm.output,\n        )\n\n    def test_054_poll(self):\n        \"\"\"CAhandler.poll() poll_identifier is none\"\"\"\n        self.assertEqual(\n            (None, None, None, None, False),\n            self.cahandler.poll(\"cert_name\", None, \"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._request_poll\")\n    def test_055_poll(self, mock_poll):\n        \"\"\"CAhandler.poll() poll_identifier is none\"\"\"\n        mock_poll.return_value = (\n            \"error\",\n            \"cert_bundle\",\n            \"cert_raw\",\n            \"poll_identifier\",\n            \"rejected\",\n        )\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\", \"rejected\"),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_056__loop_poll(self):\n        \"\"\"CAhandler._loop_poll() - no request url\"\"\"\n        request_url = None\n        self.assertEqual(\n            (None, None, None, None), self.cahandler._loop_poll(request_url)\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_057__loop_poll(self, mock_get, mock_sleep):\n        \"\"\"CAhandler._loop_poll() - nothing come back from request get\"\"\"\n        self.cahandler.polling_timeout = 5\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mock_sleep.return_value = mockresponse\n        mockresponse.json = lambda: {}\n        self.assertEqual(\n            (None, None, None, \"request_url\"), self.cahandler._loop_poll(request_url)\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_058__loop_poll(self, mock_get, mock_sleep):\n        \"\"\"CAhandler._loop_poll() - no status returned from  request get\"\"\"\n        self.cahandler.polling_timeout = 5\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mock_sleep.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual(\n            (None, None, None, \"request_url\"), self.cahandler._loop_poll(request_url)\n        )\n\n    @patch(\"requests.get\")\n    def test_059__loop_poll(self, mock_get):\n        \"\"\"CAhandler._loop_poll() - status \"rejected\" returned from  request get\"\"\"\n        self.cahandler.polling_timeout = 6\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mockresponse.json = lambda: {\"status\": \"rejected\", \"foo\": \"bar\"}\n        self.assertEqual(\n            (\"Request rejected by operator\", None, None, None),\n            self.cahandler._loop_poll(request_url),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_060__loop_poll(self, mock_get, mock_sleep):\n        \"\"\"CAhandler._loop_poll() - status \"accepted\" returned from  request get but no certificate in\"\"\"\n        self.cahandler.polling_timeout = 6\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mock_sleep.return_value = mockresponse\n        mockresponse.json = lambda: {\"status\": \"accepted\", \"foo\": \"bar\"}\n        self.assertEqual(\n            (\"Request accepted but no certificate returned\", None, None, \"request_url\"),\n            self.cahandler._loop_poll(request_url),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_061__loop_poll(self, mock_get, mock_sleep):\n        \"\"\"CAhandler._loop_poll() - status \"accepted\" returned from  request \"certifiate\" in but no \"certificateBase64\" in 2dn request\"\"\"\n        self.cahandler.polling_timeout = 6\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mock_sleep.return_value = mockresponse\n        mockresponse.json = lambda: {\n            \"status\": \"accepted\",\n            \"foo\": \"bar\",\n            \"certificate\": \"certificate\",\n        }\n        self.assertEqual(\n            (\n                \"Request accepted but no certificateBase64 returned\",\n                None,\n                None,\n                \"request_url\",\n            ),\n            self.cahandler._loop_poll(request_url),\n        )\n\n    @patch(\n        \"examples.ca_handler.certifier_ca_handler.CAhandler._pem_cert_chain_generate\"\n    )\n    @patch(\"requests.get\")\n    def test_062__loop_poll(self, mock_get, mock_chain):\n        \"\"\"CAhandler._loop_poll() - status \"accepted\" returned from  request \"certifiate\" in but no \"certificateBase64\" in 2dn request\"\"\"\n        self.cahandler.polling_timeout = 6\n        self.cahandler.timeout = 0\n        request_url = \"request_url\"\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mockresponse.json = lambda: {\n            \"status\": \"accepted\",\n            \"foo\": \"bar\",\n            \"certificate\": \"certificate\",\n            \"certificateBase64\": \"certificateBase64\",\n        }\n        mock_chain.return_value = \"foo\"\n        self.assertEqual(\n            (None, \"foo\", \"certificateBase64\", None),\n            self.cahandler._loop_poll(request_url),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_063_enroll(self, mock_certget):\n        \"\"\"CAhandler.enroll() _cert_get returns None\"\"\"\n        mock_certget.return_value = {}\n        self.assertEqual(\n            (\"internal error\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_064_enroll(self, mock_certget):\n        \"\"\"CAhandler.enroll() _cert_get returns wrong information\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\"}\n        self.assertEqual(\n            (\"no certificate information found\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_065_enroll(self, mock_certget):\n        \"\"\"CAhandler.enroll() _cert_get returns status without error message\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"status\": \"foo\"}\n        self.assertEqual(\n            (\"unknown error\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_066_enroll(self, mock_certget):\n        \"\"\"CAhandler.enroll() _cert_get returns status with error message\"\"\"\n        mock_certget.return_value = {\n            \"foo\": \"bar\",\n            \"status\": \"foo\",\n            \"message\": \"message\",\n        }\n        self.assertEqual((\"message\", None, None, None), self.cahandler.enroll(\"csr\"))\n\n    @patch(\n        \"examples.ca_handler.certifier_ca_handler.CAhandler._pem_cert_chain_generate\"\n    )\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_067_enroll(self, mock_certget, mock_chain):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\n            \"foo\": \"bar\",\n            \"certificateBase64\": \"certificateBase64\",\n        }\n        mock_chain.return_value = \"mock_chain\"\n        self.assertEqual(\n            (None, \"mock_chain\", \"certificateBase64\", None),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._loop_poll\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_068_enroll(self, mock_certget, mock_loop):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_loop.return_value = (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\")\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\"),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._loop_poll\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_069_enroll(self, mock_certget, mock_loop, mock_prof):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_loop.return_value = (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\")\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\"),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._loop_poll\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_070_enroll(self, mock_certget, mock_loop, mock_prof):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_loop.return_value = (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\")\n        mock_prof.return_value = None\n        self.cahandler.eab_profiling = True\n        self.cahandler.header_info_field = \"header_info_field\"\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\"),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._loop_poll\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_071_enroll(self, mock_certget, mock_loop, mock_prof):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_loop.return_value = (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\")\n        self.cahandler.eab_profiling = True\n        self.cahandler.header_info_field = \"header_info_field\"\n        mock_prof.return_value = \"prof_error\"\n        self.assertEqual((\"prof_error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_prof.called)\n        self.assertFalse(mock_certget.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._loop_poll\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get\")\n    def test_072_enroll(self, mock_certget, mock_loop, mock_prof):\n        \"\"\"CAhandler.enroll() _cert_get returns certb64\"\"\"\n        mock_certget.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_loop.return_value = (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\")\n        self.cahandler.eab_profiling = False\n        self.cahandler.header_info_field = \"header_info_field\"\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_identifier\"),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_prof.called)\n        self.assertTrue(mock_certget.called)\n        self.assertEqual(self.cahandler.profile_id, None)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_073_revoke(self, mock_getca):\n        \"\"\"CAhandler.revoke() _ca_get_properties returns nothing\"\"\"\n        mock_getca.return_value = {}\n        self.assertEqual(\n            (404, \"urn:ietf:params:acme:error:serverInternal\", \"CA could not be found\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_074_revoke(self, mock_getca):\n        \"\"\"CAhandler.revoke() _ca_get_properties returns wrong information\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\"}\n        self.assertEqual(\n            (404, \"urn:ietf:params:acme:error:serverInternal\", \"CA could not be found\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_075_revoke(self, mock_getca, mock_serial):\n        \"\"\"CAhandler.revoke() _ca_get_properties cert_serial_get failed\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = None\n        self.assertEqual(\n            (\n                404,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"failed to get serial number from cert\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_076_revoke(self, mock_getca, mock_serial, mock_getcert):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties failed\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {}\n        self.assertEqual(\n            (\n                404,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Cert could not be found\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_077_revoke(self, mock_getca, mock_serial, mock_getcert):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns wrong information\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\"foo\": \"bar\"}\n        self.assertEqual(\n            (\n                404,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Cert could not be found\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_078_revoke(self, mock_getca, mock_serial, mock_getcert):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties empty cert_list\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\"foo\": \"bar\", \"certificates\": []}\n        self.assertEqual(\n            (\n                404,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Cert path could not be found\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_079_revoke(self, mock_getca, mock_serial, mock_getcert):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns cert_list with wrong information\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\"foo\": \"bar\", \"certificates\": [{\"foo\": \"bar\"}]}\n        self.assertEqual(\n            (\n                404,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Cert path could not be found\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_080_revoke(\n        self, mock_getca, mock_serial, mock_getcert, mock_post, mock_eab\n    ):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns cert_list revocation successful\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\n            \"foo\": \"bar\",\n            \"certificates\": [{\"foo\": \"bar\", \"href\": \"href\"}],\n        }\n        mock_post.return_value = {}\n        self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_081_revoke(\n        self, mock_getca, mock_serial, mock_getcert, mock_post, mock_eab\n    ):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns cert_list revocation successful\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        self.cahandler.eab_profiling = True\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\n            \"foo\": \"bar\",\n            \"certificates\": [{\"foo\": \"bar\", \"href\": \"href\"}],\n        }\n        mock_post.return_value = {}\n        self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_082_revoke(self, mock_getca, mock_serial, mock_getcert, mock_post):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns href. revocation returns status without message\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\n            \"foo\": \"bar\",\n            \"certificates\": [{\"foo\": \"bar\", \"href\": \"href\"}],\n        }\n        mock_post.return_value = {\"foo\": \"bar\", \"status\": \"status\"}\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:alreadyRevoked\", \"no details\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    def test_083_revoke(self, mock_getca, mock_serial, mock_getcert, mock_post):\n        \"\"\"CAhandler.revoke() _ca_get_properties get_cert_properties returns href. revocation returns status with message\"\"\"\n        mock_getca.return_value = {\"foo\": \"bar\", \"href\": \"href\"}\n        mock_serial.return_value = 123\n        mock_getcert.return_value = {\n            \"foo\": \"bar\",\n            \"certificates\": [{\"foo\": \"bar\", \"href\": \"href\"}],\n        }\n        mock_post.return_value = {\n            \"foo\": \"bar\",\n            \"status\": \"status\",\n            \"message\": \"message\",\n        }\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:alreadyRevoked\", \"message\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    def test_084_trigger(self):\n        \"\"\"CAhandler.trigger() - no payload given\"\"\"\n        payload = None\n        self.assertEqual(\n            (\"No payload given\", None, None), self.cahandler.trigger(payload)\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_encode\")\n    def test_085_trigger(self, mock_b64dec, mock_b64enc, mock_p2d, mock_caprop):\n        \"\"\"CAhandler.trigger() - payload  but ca_lookup failed\"\"\"\n        payload = \"foo\"\n        mock_b64dec.return_value = \"foodecode\"\n        mock_p2d.return_value = \"p2d\"\n        mock_caprop.return_value = {}\n        self.assertEqual(\n            (\"Cannot find CA\", None, \"foodecode\"), self.cahandler.trigger(payload)\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_encode\")\n    def test_086_trigger(\n        self, mock_b64dec, mock_b64enc, mock_p2d, mock_caprop, mock_serial\n    ):\n        \"\"\"CAhandler.trigger() - payload serial number lookup failed\"\"\"\n        payload = \"foo\"\n        mock_b64dec.return_value = \"foodecode\"\n        mock_serial.return_value = None\n        mock_p2d.return_value = \"p2d\"\n        mock_caprop.return_value = {\"href\": \"href\"}\n        self.assertEqual(\n            (\"serial number lookup via rest failed\", None, \"foodecode\"),\n            self.cahandler.trigger(payload),\n        )\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_encode\")\n    def test_087_trigger(\n        self,\n        mock_b64dec,\n        mock_b64enc,\n        mock_p2d,\n        mock_caprop,\n        mock_serial,\n        mock_certprop,\n    ):\n        \"\"\"CAhandler.trigger() - payload serial number lookup failed\"\"\"\n        payload = \"foo\"\n        mock_b64dec.return_value = \"foodecode\"\n        mock_serial.return_value = 123\n        mock_p2d.return_value = \"p2d\"\n        mock_caprop.return_value = {\"href\": \"href\"}\n        mock_certprop.return_value = {}\n        self.assertEqual(\n            (\"no certifcates found in rest query\", None, \"foodecode\"),\n            self.cahandler.trigger(payload),\n        )\n\n    @patch(\n        \"examples.ca_handler.certifier_ca_handler.CAhandler._pem_cert_chain_generate\"\n    )\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_encode\")\n    def test_088_trigger(\n        self,\n        mock_b64dec,\n        mock_b64enc,\n        mock_p2d,\n        mock_caprop,\n        mock_serial,\n        mock_certprop,\n        mock_chain,\n    ):\n        \"\"\"CAhandler.trigger() - payload serial number lookup failed\"\"\"\n        payload = \"foo\"\n        mock_b64dec.return_value = \"foodecode\"\n        mock_serial.return_value = 123\n        mock_p2d.return_value = \"p2d\"\n        mock_caprop.return_value = {\"href\": \"href\"}\n        mock_certprop.return_value = {\"certificates\": [{\"foo\": \"bar\"}]}\n        mock_chain.return_value = \"chain\"\n        self.assertEqual((None, \"chain\", \"foodecode\"), self.cahandler.trigger(payload))\n\n    @patch(\n        \"examples.ca_handler.certifier_ca_handler.CAhandler._pem_cert_chain_generate\"\n    )\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._cert_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.CAhandler._ca_get_properties\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.certifier_ca_handler.b64_encode\")\n    def test_089_trigger(\n        self,\n        mock_b64dec,\n        mock_b64enc,\n        mock_p2d,\n        mock_caprop,\n        mock_serial,\n        mock_certprop,\n        mock_chain,\n    ):\n        \"\"\"CAhandler.trigger() - payload serial number lookup failed\"\"\"\n        payload = \"foo\"\n        mock_b64dec.return_value = \"foodecode\"\n        mock_serial.return_value = 123\n        mock_p2d.side_effect = Exception(\"p2d\")\n        mock_caprop.return_value = {\"href\": \"href\"}\n        mock_certprop.return_value = {\"certificates\": [{\"foo\": \"bar\"}]}\n        mock_chain.return_value = \"chain\"\n        self.assertEqual((None, \"chain\", \"foodecode\"), self.cahandler.trigger(payload))\n\n    def test_090__pem_cert_chain_generate(self):\n        \"\"\"_pem_cert_chain_generate - empty cert_dic\"\"\"\n        cert_dic = {}\n        self.assertFalse(self.cahandler._pem_cert_chain_generate(cert_dic))\n\n    def test_091__pem_cert_chain_generate(self):\n        \"\"\"_pem_cert_chain_generate - wrong dic\"\"\"\n        cert_dic = {\"foo\": \"bar\"}\n        self.assertFalse(self.cahandler._pem_cert_chain_generate(cert_dic))\n\n    def test_092__pem_cert_chain_generate(self):\n        \"\"\"_pem_cert_chain_generate - certificateBase64 in dict\"\"\"\n        cert_dic = {\"certificateBase64\": \"certificateBase64\"}\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase64\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    @patch(\"requests.get\")\n    def test_093__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - issuer in dict without certificateBase64\"\"\"\n        cert_dic = {\"issuer\": \"issuer\"}\n        mockresponse = Mock()\n        mock_get.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertFalse(self.cahandler._pem_cert_chain_generate(cert_dic))\n\n    @patch(\"requests.get\")\n    def test_094__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - request returns \"certificates\" but no active\"\"\"\n        cert_dic = {\"issuer\": \"issuer\", \"certificateBase64\": \"certificateBase641\"}\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"certificates\": \"certificates\"}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mock_get.side_effect = [mockresponse1, mockresponse2]\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase641\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    @patch(\"requests.get\")\n    def test_095__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - request returns certificate and active, 2nd request is bogus\"\"\"\n        cert_dic = {\"issuer\": \"issuer\", \"certificateBase64\": \"certificateBase641\"}\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"certificates\": {\"active\": \"active\"}}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mock_get.side_effect = [mockresponse1, mockresponse2]\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase641\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    @patch(\"requests.get\")\n    def test_096__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - request returns certificate two certs\"\"\"\n        cert_dic = {\"issuer\": \"issuer\", \"certificateBase64\": \"certificateBase641\"}\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"certificates\": {\"active\": \"active\"}}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\n            \"certificateBase64\": \"certificateBase642\",\n            \"issuer\": \"issuer\",\n        }\n        mockresponse3 = Mock()\n        mockresponse3.json = lambda: {\"foo\": \"bar\"}\n        mock_get.side_effect = [mockresponse1, mockresponse2, mockresponse3]\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase641\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\ncertificateBase642\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    @patch(\"requests.get\")\n    def test_097__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - request returns certificate three certs\"\"\"\n        cert_dic = {\"issuer\": \"issuer\", \"certificateBase64\": \"certificateBase641\"}\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"certificates\": {\"active\": \"active\"}}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\n            \"certificateBase64\": \"certificateBase642\",\n            \"issuer\": \"issuer\",\n        }\n        mockresponse3 = Mock()\n        mockresponse3.json = lambda: {\"certificates\": {\"active\": \"active\"}}\n        mockresponse4 = Mock()\n        mockresponse4.json = lambda: {\n            \"certificateBase64\": \"certificateBase643\",\n            \"issuer\": \"issuer\",\n        }\n        mockresponse5 = Mock()\n        mockresponse5.json = lambda: {\"foo\": \"bar\"}\n        mock_get.side_effect = [\n            mockresponse1,\n            mockresponse2,\n            mockresponse3,\n            mockresponse4,\n            mockresponse5,\n        ]\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase641\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\ncertificateBase642\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\ncertificateBase643\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    @patch(\"requests.get\")\n    def test_098__pem_cert_chain_generate(self, mock_get):\n        \"\"\"_pem_cert_chain_generate - issuerCa in\"\"\"\n        cert_dic = {\"issuerCa\": \"issuerCa\", \"certificateBase64\": \"certificateBase641\"}\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"certificates\": \"certificates\"}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mock_get.side_effect = [mockresponse1, mockresponse2]\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncertificateBase641\\n-----END CERTIFICATE-----\\n\",\n            self.cahandler._pem_cert_chain_generate(cert_dic),\n        )\n\n    def test_099__enter__(self):\n        \"\"\"test __enter__\"\"\"\n        self.cahandler.__enter__()\n\n    @patch(\"requests.get\")\n    def test_100_request_poll(self, mock_get):\n        \"\"\"test request poll request returned exception\"\"\"\n        mock_get.side_effect = Exception(\"exc_api_get\")\n        result = ('\"status\" field not found in response.', None, None, \"url\", False)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Polling request returned an error: exc_api_get\", lcm.output\n        )\n\n    @patch(\"requests.get\")\n    def test_101_request_poll(self, mock_get):\n        \"\"\"test request poll request returned unknown status\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"unknown\"}\n        mock_get.return_value = mockresponse\n        result = (\"Unknown request status: unknown\", None, None, \"url\", False)\n        self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n\n    @patch(\"requests.get\")\n    def test_102_request_poll(self, mock_get):\n        \"\"\"test request poll request returned status rejected\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"rejected\"}\n        mock_get.return_value = mockresponse\n        result = (\"Request rejected by operator\", None, None, \"url\", True)\n        self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n\n    @patch(\"requests.get\")\n    def test_103_request_poll(self, mock_get):\n        \"\"\"test request poll request returned status accepted but no certinformation in\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"accepted\", \"foo\": \"bar\"}\n        mock_get.return_value = mockresponse\n        result = (\n            \"No certificate structure in request response\",\n            None,\n            None,\n            \"url\",\n            False,\n        )\n        self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n\n    @patch(\"requests.get\")\n    def test_104_request_poll(self, mock_get):\n        \"\"\"test request poll request returned status accepted but no certinformation in\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"accepted\", \"certificate\": \"certificate\"}\n        mock_get.return_value = mockresponse\n        result = (\n            \"certificateBase64 is missing in cert request response\",\n            None,\n            None,\n            \"url\",\n            False,\n        )\n        self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n\n    @patch(\n        \"examples.ca_handler.certifier_ca_handler.CAhandler._pem_cert_chain_generate\"\n    )\n    @patch(\"requests.get\")\n    def test_105_request_poll(self, mock_get, mock_pemgen):\n        \"\"\"test request poll request returned status accepted but no certinformation in\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"status\": \"accepted\",\n            \"certificate\": \"certificate\",\n            \"certificateBase64\": \"certificateBase64\",\n        }\n        mock_get.return_value = mockresponse\n        mock_pemgen.return_value = \"bundle\"\n        result = (None, \"bundle\", \"certificateBase64\", \"url\", False)\n        self.assertEqual(result, self.cahandler._request_poll(\"url\"))\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    def test_106_csr_check(self, mock_eab):\n        \"\"\"test csr_check\"\"\"\n        csr = \"csr\"\n        mock_eab.return_value = None\n        self.assertEqual(None, self.cahandler._csr_check(csr))\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.eab_profile_header_info_check\")\n    def test_107_csr_check(self, mock_eab):\n        \"\"\"test csr_check\"\"\"\n        csr = \"csr\"\n        mock_eab.return_value = \"mock_eab\"\n        self.assertEqual(\"mock_eab\", self.cahandler._csr_check(csr))\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.certifier_ca_handler.handler_config_check\")\n    def test_108_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    if os.path.exists(\"acme_test.db\"):\n        os.remove(\"acme_test.db\")\n    unittest.main()\n"
  },
  {
    "path": "test/test_challenge.py",
    "content": "import sys\nimport unittest\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\nfrom unittest.mock import Mock, patch, MagicMock\n\n\nclass TestChallengeConfiguration(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Set up module-level mocks before any tests run\"\"\"\n        # Mock the missing db_handler module\n        mock_db_handler = MagicMock()\n        mock_dbstore_class = MagicMock()\n        mock_db_handler.DBstore = mock_dbstore_class\n        sys.modules[\"acme_srv.db_handler\"] = mock_db_handler\n\n        # Import after mocking\n        from acme_srv.challenge import ChallengeConfiguration\n\n        cls.ChallengeConfiguration = ChallengeConfiguration\n\n    @classmethod\n    def tearDownClass(cls):\n        \"\"\"Clean up module mocks\"\"\"\n        if \"acme_srv.db_handler\" in sys.modules:\n            del sys.modules[\"acme_srv.db_handler\"]\n        if \"acme_srv.challenge\" in sys.modules:\n            del sys.modules[\"acme_srv.challenge\"]\n\n    def test_001_configuration_defaults(self):\n        config = self.ChallengeConfiguration()\n        self.assertFalse(config.validation_disabled)\n        self.assertEqual(config.validation_timeout, 10)\n        self.assertIsNone(config.dns_server_list)\n        self.assertEqual(config.dns_validation_pause_timer, 0.5)\n        self.assertIsNone(config.proxy_server_list)\n        self.assertFalse(config.sectigo_sim)\n        self.assertFalse(config.tnauthlist_support)\n        self.assertFalse(config.email_identifier_support)\n        self.assertIsNone(config.email_address)\n        self.assertFalse(config.forward_address_check)\n        self.assertFalse(config.reverse_address_check)\n        self.assertIsNone(config.source_address)\n        self.assertFalse(config.eab_profiling)\n\n\nclass TestDatabaseChallengeRepository(unittest.TestCase):\n    def setUp(self):\n        import logging\n\n        # Mock the missing db_handler module if not already done\n        if \"acme_srv.db_handler\" not in sys.modules:\n            mock_db_handler = MagicMock()\n            mock_dbstore_class = MagicMock()\n            mock_db_handler.DBstore = mock_dbstore_class\n            sys.modules[\"acme_srv.db_handler\"] = mock_db_handler\n\n        # Import after ensuring mocking\n        from acme_srv.challenge import DatabaseChallengeRepository\n        from acme_srv.challenge_error_handling import DatabaseError, ValidationError\n        from acme_srv.challenge_business_logic import (\n            ChallengeInfo,\n            ChallengeCreationRequest,\n            ChallengeUpdateRequest,\n        )\n\n        # Store imports as instance variables\n        self.DatabaseChallengeRepository = DatabaseChallengeRepository\n        self.DatabaseError = DatabaseError\n        self.ValidationError = ValidationError\n        self.ChallengeInfo = ChallengeInfo\n        self.ChallengeCreationRequest = ChallengeCreationRequest\n        self.ChallengeUpdateRequest = ChallengeUpdateRequest\n\n        self.dbstore = Mock()\n        # Create a real logger for testing\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.logger.setLevel(logging.DEBUG)\n        # Remove any existing handlers to avoid duplicate logs\n        for handler in self.logger.handlers[:]:\n            self.logger.removeHandler(handler)\n\n        self.repo = self.DatabaseChallengeRepository(self.dbstore, self.logger)\n\n    def test_002_find_challenges_by_authorization_success(self):\n        self.dbstore.challenges_search.return_value = [\n            {\"name\": \"c1\", \"type\": \"dns-01\", \"status__name\": \"pending\", \"token\": \"tok1\"}\n        ]\n        result = self.repo.find_challenges_by_authorization(\"authz1\")\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0].name, \"c1\")\n        self.dbstore.challenges_search.assert_called_once()\n\n    def test_003_find_challenges_by_authorization_db_error(self):\n        self.dbstore.challenges_search.side_effect = Exception(\"db fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.repo.find_challenges_by_authorization(\"authz1\")\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to search for challenges: db fail\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_004_get_challengeinfo_by_challengename_success(self):\n        self.dbstore.challenge_lookup.return_value = {\"name\": \"c1\", \"type\": \"dns-01\"}\n        result = self.repo.get_challengeinfo_by_challengename(\"c1\")\n        self.assertEqual(result[\"name\"], \"c1\")\n\n    def test_005_get_challengeinfo_by_challengename_db_error(self):\n        self.dbstore.challenge_lookup.side_effect = Exception(\"db fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.repo.get_challengeinfo_by_challengename(\"c1\")\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to lookup challenge keyauthorization: db fail\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_006_get_challenge_by_name_success(self):\n        self.dbstore.challenge_lookup.return_value = {\n            \"type\": \"dns-01\",\n            \"token\": \"tok\",\n            \"status\": \"valid\",\n            \"authorization__name\": \"authz\",\n            \"authorization__type\": \"dns\",\n            \"authorization__value\": \"val\",\n            \"validated\": 123456,\n        }\n        with patch(\n            \"acme_srv.challenge.uts_to_date_utc\", return_value=\"2021-01-01T00:00:00Z\"\n        ):\n            result = self.repo.get_challenge_by_name(\"c1\")\n            self.assertEqual(result.name, \"c1\")\n            self.assertEqual(result.status, \"valid\")\n            self.assertEqual(result.validated, \"2021-01-01T00:00:00Z\")\n\n    def test_007_get_challenge_by_name_db_error(self):\n        self.dbstore.challenge_lookup.side_effect = Exception(\"db fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.repo.get_challenge_by_name(\"c1\")\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to lookup challenge: db fail\" in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_008_create_challenge_success(self):\n        self.dbstore.challenge_add.return_value = 1\n        with patch(\n            \"acme_srv.challenge.generate_random_string\", return_value=\"c1\"\n        ), patch(\"acme_srv.challenge.uts_now\", return_value=1000):\n            req = self.ChallengeCreationRequest(\"dns-01\", \"tok\", \"authz\", \"val\")\n            name = self.repo.create_challenge(req)\n            self.assertEqual(name, \"c1\")\n\n    def test_009_create_challenge_db_error(self):\n        self.dbstore.challenge_add.side_effect = Exception(\"db fail\")\n        with patch(\n            \"acme_srv.challenge.generate_random_string\", return_value=\"c1\"\n        ), patch(\"acme_srv.challenge.uts_now\", return_value=1000):\n            req = self.ChallengeCreationRequest(\"dns-01\", \"tok\", \"authz\", \"val\")\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n                with self.assertRaises(self.DatabaseError):\n                    self.repo.create_challenge(req)\n            # Verify the critical log message was generated\n            self.assertTrue(\n                any(\n                    \"Database error: failed to add new challenge: db fail\"\n                    in record.message\n                    for record in log_context.records\n                    if record.levelname == \"CRITICAL\"\n                )\n            )\n\n    def test_010_update_challenge_success(self):\n        self.dbstore.challenge_update.return_value = None\n        req = self.ChallengeUpdateRequest(\"c1\", status=2)\n        self.assertTrue(self.repo.update_challenge(req))\n\n    def test_011_update_challenge_db_error(self):\n        self.dbstore.challenge_update.side_effect = Exception(\"db fail\")\n        req = self.ChallengeUpdateRequest(\"c1\", status=2)\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.repo.update_challenge(req)\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to update challenge: db fail\" in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_012_update_authorization_status_success(self):\n        self.dbstore.challenge_lookup.return_value = {\"authorization\": \"authz1\"}\n        self.dbstore.authorization_update.return_value = None\n        self.assertTrue(self.repo.update_authorization_status(\"c1\", \"valid\"))\n\n    def test_013_update_authorization_status_db_error(self):\n        self.dbstore.challenge_lookup.side_effect = Exception(\"db fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.repo.update_authorization_status(\"c1\", \"valid\")\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to update authorization: db fail\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_014_get_account_jwk_success(self):\n        self.dbstore.challenge_lookup.return_value = {\n            \"authorization__order__account__name\": \"acc1\"\n        }\n        self.dbstore.jwk_load.return_value = {\"kty\": \"RSA\"}\n        self.assertEqual(self.repo.get_account_jwk(\"c1\"), {\"kty\": \"RSA\"})\n\n    def test_015_get_account_jwk_none(self):\n        self.dbstore.challenge_lookup.return_value = {}\n        self.assertIsNone(self.repo.get_account_jwk(\"c1\"))\n\n\nclass TestChallenge(unittest.TestCase):\n    def setUp(self):\n        import logging\n\n        # Mock the missing db_handler module if not already done\n        if \"acme_srv.db_handler\" not in sys.modules:\n            mock_db_handler = MagicMock()\n            mock_dbstore_class = MagicMock()\n            mock_db_handler.DBstore = mock_dbstore_class\n            sys.modules[\"acme_srv.db_handler\"] = mock_db_handler\n\n        # Import after ensuring mocking\n        from acme_srv.challenge import (\n            Challenge,\n            ChallengeConfiguration,\n            DatabaseChallengeRepository,\n        )\n        from acme_srv.challenge_error_handling import (\n            DatabaseError,\n            ValidationError,\n            UnsupportedChallengeTypeError,\n        )\n        from acme_srv.challenge_business_logic import (\n            ChallengeInfo,\n            ChallengeCreationRequest,\n            ChallengeUpdateRequest,\n        )\n\n        # Store imports as instance variables for use in tests\n        self.Challenge = Challenge\n        self.ChallengeConfiguration = ChallengeConfiguration\n        self.DatabaseChallengeRepository = DatabaseChallengeRepository\n        self.DatabaseError = DatabaseError\n        self.ValidationError = ValidationError\n        self.UnsupportedChallengeTypeError = UnsupportedChallengeTypeError\n        self.ChallengeInfo = ChallengeInfo\n        self.ChallengeCreationRequest = ChallengeCreationRequest\n        self.ChallengeUpdateRequest = ChallengeUpdateRequest\n        self.DatabaseError = DatabaseError\n        self.ValidationError = ValidationError\n        self.UnsupportedChallengeTypeError = UnsupportedChallengeTypeError\n        self.ChallengeInfo = ChallengeInfo\n        self.ChallengeCreationRequest = ChallengeCreationRequest\n        self.ChallengeUpdateRequest = ChallengeUpdateRequest\n\n        # Create a real logger for testing\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.logger.setLevel(logging.DEBUG)\n        # Remove any existing handlers to avoid duplicate logs\n        for handler in self.logger.handlers[:]:\n            self.logger.removeHandler(handler)\n\n        self.challenge = Challenge(debug=True, logger=self.logger, srv_name=\"srv\")\n        self.challenge.dbstore = Mock()\n        self.challenge.repository = Mock()\n        self.challenge.message = Mock()\n        self.challenge.error_handler = Mock()\n        self.challenge.state_manager = Mock()\n        self.challenge.validator_registry = Mock()\n        self.challenge.config = self.ChallengeConfiguration()\n        self.challenge.config.dns_server_list = [\"8.8.8.8\"]\n        self.challenge.config.proxy_server_list = {\"http\": \"proxy\"}\n        self.challenge.config.validation_timeout = 1\n        self.challenge.server_name = \"srv\"\n        self.challenge.path_dic = {\n            \"chall_path\": \"/acme/chall/\",\n            \"authz_path\": \"/acme/authz/\",\n        }\n\n    def test_016_create_error_response(self):\n        self.challenge.message.prepare_response.return_value = {\"status\": \"error\"}\n        resp = self.challenge._create_error_response(400, \"bad\", \"fail\")\n        self.assertEqual(resp[\"status\"], \"error\")\n\n    def test_017_create_success_response(self):\n        self.challenge.message.prepare_response.return_value = {\"status\": \"ok\"}\n        resp = self.challenge._create_success_response({\"foo\": \"bar\"})\n        self.assertEqual(resp[\"status\"], \"ok\")\n\n    def test_018_extract_challenge_name_from_url(self):\n        with patch(\n            \"acme_srv.challenge.parse_url\", return_value={\"path\": \"/acme/chall/c1\"}\n        ):\n            name = self.challenge._extract_challenge_name_from_url(\"/acme/chall/c1\")\n            self.assertEqual(name, \"c1\")\n\n    def test_019_get_challenge_validation_details_success(self):\n        self.challenge.dbstore.challenge_lookup.return_value = {\n            \"type\": \"dns-01\",\n            \"token\": \"tok\",\n            \"keyauthorization\": \"kauth\",\n            \"authorization__type\": \"dns\",\n            \"authorization__value\": \"val\",\n        }\n        self.challenge.repository.get_account_jwk.return_value = {\"kty\": \"RSA\"}\n        with patch(\"acme_srv.challenge.jwk_thumbprint_get\", return_value=\"thumb\"):\n            details = self.challenge._get_challenge_validation_details(\"c1\")\n            self.assertEqual(details[\"jwk_thumbprint\"], \"thumb\")\n\n    def test_020_get_challenge_validation_details_no_challenge(self):\n        self.challenge.dbstore.challenge_lookup.return_value = None\n        self.assertIsNone(self.challenge._get_challenge_validation_details(\"c1\"))\n\n    def test_021_get_challenge_validation_details_no_pubkey(self):\n        self.challenge.dbstore.challenge_lookup.return_value = {\"type\": \"dns-01\"}\n        self.challenge.repository.get_account_jwk.return_value = None\n        self.assertIsNone(self.challenge._get_challenge_validation_details(\"c1\"))\n\n    def test_022_get_challenge_validation_details_exception(self):\n        self.challenge.dbstore.challenge_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertIsNone(self.challenge._get_challenge_validation_details(\"c1\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to get challenge validation details: fail\",\n            lcm.output,\n        )\n\n    def test_023_handle_challenge_validation_request_valid(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        info.status = \"pending\"\n        self.challenge.config.tnauthlist_support = False\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge._start_async_validation = Mock()\n        self.challenge._create_success_response = Mock(return_value={\"status\": \"ok\"})\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {}, {\"url\": \"u\"}, \"c1\", info\n        )\n        self.assertEqual(resp[\"status\"], \"ok\")\n\n    def test_024_handle_challenge_validation_request_tnauthlist(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"tkauth-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        info.status = \"pending\"\n        self.challenge.config.tnauthlist_support = True\n        self.challenge._validate_tnauthlist_payload = Mock(return_value={\"code\": 200})\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge._start_async_validation = Mock()\n        self.challenge._create_success_response = Mock(return_value={\"status\": \"ok\"})\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {\"atc\": \"foo\"}, {\"url\": \"u\"}, \"c1\", info\n        )\n        self.assertEqual(resp[\"status\"], \"ok\")\n\n    def test_025_handle_challenge_validation_request_tnauthlist_fail(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"tkauth-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        info.status = \"pending\"\n        self.challenge.config.tnauthlist_support = True\n        self.challenge._validate_tnauthlist_payload = Mock(\n            return_value={\"code\": 400, \"error\": \"fail\"}\n        )\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {\"atc\": None}, {\"url\": \"u\"}, \"c1\", info\n        )\n        self.assertEqual(resp[\"code\"], 400)\n\n    def test_026_handle_validation_disabled(self):\n        self.challenge.config.forward_address_check = False\n        self.challenge.config.reverse_address_check = False\n        self.challenge.state_manager.transition_to_valid = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertTrue(self.challenge._handle_validation_disabled(\"c1\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Source address checks are disabled. Setting challenge status to valid. This is not recommended as this is a severe security risk!\",\n            lcm.output,\n        )\n\n    def test_027_handle_validation_disabled_invalid(self):\n        self.challenge.config.forward_address_check = True\n        self.challenge._perform_source_address_validation = Mock(\n            return_value=(False, True, \"fail\")\n        )\n        self.challenge.state_manager.transition_to_invalid = Mock()\n        self.assertFalse(self.challenge._handle_validation_disabled(\"c1\"))\n\n    def test_028_load_address_check_configuration(self):\n        import logging\n        from configparser import ConfigParser\n\n        config_dic = ConfigParser()\n        config_dic.add_section(\"Challenge\")\n        config_dic.set(\"Challenge\", \"forward_address_check\", \"True\")\n        config_dic.set(\"Challenge\", \"reverse_address_check\", \"True\")\n        config_dic.set(\"Challenge\", \"challenge_validation_disable\", \"True\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.challenge._load_address_check_configuration(config_dic)\n        self.assertTrue(self.challenge.config.forward_address_check)\n        self.assertTrue(self.challenge.config.reverse_address_check)\n        self.assertTrue(self.challenge.config.validation_disabled)\n        self.assertIn(\n            \"INFO:test_a2c:Challenge validation is globally disabled.\", lcm.output\n        )\n\n    def test_029_load_dns_configuration(self):\n        config_dic = {\n            \"Challenge\": {\n                \"dns_server_list\": '[\"8.8.8.8\"]',\n                \"dns_validation_pause_timer\": \"2\",\n            }\n        }\n        self.challenge._load_dns_configuration(config_dic)\n        self.assertEqual(self.challenge.config.dns_server_list, [\"8.8.8.8\"])\n        self.assertEqual(self.challenge.config.dns_validation_pause_timer, 2)\n\n    def test_030_load_dns_configuration_fail(self):\n        # Set to None first to test that bad configuration doesn't change it\n        self.challenge.config.dns_server_list = None\n        config_dic = {\n            \"Challenge\": {\n                \"dns_server_list\": \"badjson\",\n                \"dns_validation_pause_timer\": \"bad\",\n            }\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.challenge._load_dns_configuration(config_dic)\n        self.assertIsInstance(self.challenge.config.dns_server_list, type(None))\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load dns_server_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse dns_validation_pause_timer from configuration: invalid literal for int() with base 10: 'bad'\",\n            lcm.output,\n        )\n\n    def test_031_load_proxy_configuration(self):\n        config_dic = {\"DEFAULT\": {\"proxy_server_list\": '{\"http\": \"proxy\"}'}}\n        self.challenge._load_proxy_configuration(config_dic)\n        self.assertEqual(self.challenge.proxy_server_list, {\"http\": \"proxy\"})\n\n    def test_032_load_proxy_configuration_fail(self):\n        config_dic = {\"DEFAULT\": {\"proxy_server_list\": \"badjson\"}}\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.challenge._load_proxy_configuration(config_dic)\n        self.assertFalse(self.challenge.proxy_server_list)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load proxy_server_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_033_load_configuration(self):\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()\n        config_obj.add_section(\"Challenge\")\n        config_obj.set(\"Challenge\", \"sectigo_sim\", \"False\")\n\n        with patch(\n            \"acme_srv.challenge.load_config\", return_value=config_obj\n        ), patch.object(self.challenge, \"_load_dns_configuration\"), patch.object(\n            self.challenge, \"_load_proxy_configuration\"\n        ), patch.object(\n            self.challenge, \"_load_address_check_configuration\"\n        ), patch(\n            \"acme_srv.challenge.create_challenge_validator_registry\",\n            return_value=Mock(),\n        ), patch.object(\n            self.challenge, \"_initialize_business_logic_components\"\n        ):\n            self.challenge._load_configuration()\n            self.assertFalse(self.challenge.config.sectigo_sim)\n\n    def test_034_load_configuration_without_challengesection(self):\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()  # No Challenge section\n        config_obj.add_section(\"CAhandler\")\n        config_obj.set(\"CAhandler\", \"foo\", \"bar\")\n\n        self.challenge._load_address_check_configuration(config_obj)\n        self.assertFalse(\n            self.challenge.config.validation_disabled\n        )  # Default value should be used\n        self.assertFalse(\n            self.challenge.config.forward_address_check\n        )  # Default value should be used\n        self.assertFalse(\n            self.challenge.config.reverse_address_check\n        )  # Default value should be used\n\n    def test_035_load_configuration_with_source_address_check(self):\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()  # No Challenge section\n        config_obj.add_section(\"Challenge\")\n        config_obj.set(\"Challenge\", \"source_address_check\", \"True\")\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as lcm:\n            self.challenge._load_address_check_configuration(config_obj)\n            self.assertFalse(\n                self.challenge.config.validation_disabled\n            )  # Default value should be used\n            self.assertTrue(\n                self.challenge.config.forward_address_check\n            )  # Default value should be used\n            self.assertFalse(\n                self.challenge.config.reverse_address_check\n            )  # Default value should be used\n        self.assertIn(\n            \"WARNING:test_a2c:source_address_check is deprecated, please use forward_address_check instead\",\n            lcm.output,\n        )\n\n    def test_036_ensure_components_initialized(self):\n        self.challenge.factory = Mock()\n        self.challenge.service = Mock()\n        self.challenge._ensure_components_initialized()  # Should not raise\n        self.challenge.factory = None\n        with self.assertRaises(RuntimeError):\n            self.challenge._ensure_components_initialized()\n\n    def test_037_perform_challenge_validation_success(self):\n        self.challenge.state_manager.transition_to_processing = Mock()\n        self.challenge.config.validation_disabled = False\n        self.challenge._execute_challenge_validation = Mock()\n        self.challenge._update_challenge_state_from_validation = Mock(return_value=True)\n        self.assertTrue(self.challenge._perform_challenge_validation(\"c1\", {}))\n\n    def test_038_perform_challenge_validation_disabled(self):\n        self.challenge.state_manager.transition_to_processing = Mock()\n        self.challenge.config.validation_disabled = True\n        self.challenge._handle_validation_disabled = Mock(return_value=True)\n        self.assertTrue(self.challenge._perform_challenge_validation(\"c1\", {}))\n\n    def test_039_perform_challenge_validation_exception(self):\n        self.challenge.state_manager.transition_to_processing = Mock()\n        self.challenge.config.validation_disabled = False\n        self.challenge._execute_challenge_validation = Mock(\n            side_effect=Exception(\"fail\")\n        )\n        self.challenge._update_challenge_state_from_validation = Mock(\n            return_value=False\n        )\n        self.challenge.error_handler.handle_error.return_value = \"fail\"\n        self.challenge.state_manager.transition_to_invalid = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.assertFalse(self.challenge._perform_challenge_validation(\"c1\", {}))\n        self.assertIn(\n            \"ERROR:test_a2c:Challenge validation error for c1: fail\", lcm.output\n        )\n\n    def test_040_perform_source_address_validation_disabled(self):\n        self.challenge.config.forward_address_check = False\n        self.challenge.config.reverse_address_check = False\n        result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertEqual(result, (True, False, None))\n\n    def test_041_perform_source_address_validation_not_found(self):\n        self.challenge.config.forward_address_check = True\n        self.challenge.repository.get_challenge_by_name.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertEqual(result, (False, True, \"Challenge not found\"))\n        self.assertIn(\"ERROR:test_a2c:Challenge not found: c1\", lcm.output)\n\n    def test_042_perform_source_address_validation_success(self):\n        self.challenge.config.forward_address_check = True\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge.validator_registry.is_supported.return_value = True\n        mock_result = Mock(success=True, invalid=False)\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n        result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertEqual(result, (True, False, None))\n\n    def test_043_perform_source_address_validation_fail(self):\n        self.challenge.config.forward_address_check = True\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge.validator_registry.is_supported.return_value = True\n        mock_result = Mock(success=False, invalid=True, error_message=\"fail\")\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertIn(\n            \"WARNING:test_a2c:Source address validation failed for c1: fail\", lcm.output\n        )\n        self.assertEqual(result, (False, True, \"fail\"))\n\n    def test_044_perform_source_address_validation_validator_not_available(self):\n        self.challenge.config.forward_address_check = True\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge.validator_registry.is_supported.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertIn(\n            \"WARNING:test_a2c:Source address validator not available\", lcm.output\n        )\n        self.assertEqual(result, (True, False, None))\n\n    def test_045_perform_source_address_validation_exception(self):\n        self.challenge.config.forward_address_check = True\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        self.challenge.repository.get_challenge_by_name.return_value = info\n        self.challenge.validator_registry.is_supported.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._perform_source_address_validation(\"c1\")\n        self.assertIn(\n            \"ERROR:test_a2c:Source address validation error for c1: fail\", lcm.output\n        )\n        self.assertEqual(\n            result, (False, True, \"Source address validation error for c1: fail\")\n        )\n\n    def test_046_perform_validation_with_retry_success(self):\n        context = Mock()\n        self.challenge.validator_registry.validate_challenge.side_effect = [\n            Mock(success=False, invalid=False),\n            Mock(success=True, invalid=False),\n        ]\n        result = self.challenge._perform_validation_with_retry(\"dns-01\", context)\n        self.assertTrue(result.success)\n\n    def test_047_perform_validation_with_retry_invalid(self):\n        context = Mock()\n        self.challenge.validator_registry.validate_challenge.return_value = Mock(\n            success=False, invalid=True\n        )\n        result = self.challenge._perform_validation_with_retry(\"dns-01\", context)\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n\n    def test_048_start_async_validation(self):\n        with patch(\"acme_srv.challenge.Thread\") as mock_thread:\n            instance = mock_thread.return_value\n            instance.join.return_value = True\n            self.challenge._perform_challenge_validation = Mock()\n            self.challenge._start_async_validation(\"c1\", {})\n            instance.start.assert_called_once()\n            instance.join.assert_called_once()\n\n    def test_049_update_challenge_state_from_validation_invalid(self):\n        validation_result = Mock(invalid=True, success=False)\n        self.challenge.state_manager.transition_to_invalid = Mock()\n        self.assertFalse(\n            self.challenge._update_challenge_state_from_validation(\n                \"c1\", validation_result\n            )\n        )\n\n    def test_050_update_challenge_state_from_validation_success(self):\n        validation_result = Mock(invalid=False, success=True)\n        self.challenge.state_manager.transition_to_valid = Mock()\n        self.assertTrue(\n            self.challenge._update_challenge_state_from_validation(\n                \"c1\", validation_result\n            )\n        )\n\n    def test_051_update_challenge_state_from_validation_inconclusive(self):\n        validation_result = Mock(invalid=False, success=False)\n        self.assertFalse(\n            self.challenge._update_challenge_state_from_validation(\n                \"c1\", validation_result\n            )\n        )\n\n    def test_052_validate_tnauthlist_payload_success(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"tkauth-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        payload = {\"atc\": \"foo\"}\n        result = self.challenge._validate_tnauthlist_payload(payload, info)\n        self.assertEqual(result[\"code\"], 200)\n\n    def test_053_validate_tnauthlist_payload_missing_atc(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"tkauth-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        payload = {}\n        self.challenge._create_error_response = Mock(\n            return_value={\"code\": 400, \"error\": \"fail\"}\n        )\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._validate_tnauthlist_payload(payload, info)\n        self.assertIn(\n            \"ERROR:test_a2c:TNauthlist payload validation failed. atc claim is missing\",\n            lcm.output,\n        )\n        self.assertEqual(result[\"code\"], 400)\n\n    def test_054_validate_tnauthlist_payload_missing_spc(self):\n        info = self.ChallengeInfo(\n            \"c1\", \"tkauth-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n        )\n        payload = {\"atc\": None}\n        self.challenge._create_error_response = Mock(\n            return_value={\"code\": 400, \"error\": \"fail\"}\n        )\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            result = self.challenge._validate_tnauthlist_payload(payload, info)\n        self.assertIn(\n            \"ERROR:test_a2c:TNauthlist payload validation failed. SPC token is missing\",\n            lcm.output,\n        )\n        self.assertEqual(result[\"code\"], 400)\n\n    def test_055_process_challenge_request_success(self):\n        self.challenge._ensure_components_initialized = Mock()\n        self.challenge.message.check.return_value = (\n            200,\n            None,\n            None,\n            {\"url\": \"u\"},\n            {\"foo\": \"bar\"},\n            \"acc\",\n        )\n        self.challenge._extract_challenge_name_from_url = Mock(return_value=\"c1\")\n        self.challenge.repository.get_challenge_by_name.return_value = (\n            self.ChallengeInfo(\n                \"c1\", \"dns-01\", \"tok\", \"pending\", \"authz\", \"dns\", \"val\", \"url\"\n            )\n        )\n        self.challenge._handle_challenge_validation_request = Mock(\n            return_value={\"status\": \"ok\"}\n        )\n        resp = self.challenge.process_challenge_request(\"content\")\n        self.assertEqual(resp[\"status\"], \"ok\")\n\n    def test_056_process_challenge_request_error(self):\n        self.challenge._ensure_components_initialized = Mock()\n        self.challenge.message.check.side_effect = Exception(\"fail\")\n        self.challenge.error_handler.handle_error.return_value = Mock()\n        self.challenge.error_handler.create_acme_error_response.return_value = {\n            \"status\": \"error\"\n        }\n        resp = self.challenge.process_challenge_request(\"content\")\n        self.assertEqual(resp[\"status\"], \"error\")\n\n    def test_057_retrieve_challenge_set_success(self):\n        self.challenge._ensure_components_initialized = Mock()\n        self.challenge.service = Mock()\n        self.challenge.service.get_challenge_set_for_authorization.return_value = [\n            {\"foo\": \"bar\"}\n        ]\n        resp = self.challenge.retrieve_challenge_set(\"authz\", \"valid\", \"tok\", False)\n        self.assertEqual(resp, [{\"foo\": \"bar\"}])\n\n    def test_058_retrieve_challenge_set_exception(self):\n        self.challenge._ensure_components_initialized = Mock()\n        self.challenge.service = Mock()\n        self.challenge.service.get_challenge_set_for_authorization.side_effect = (\n            Exception(\"fail\")\n        )\n        self.challenge.error_handler.handle_error.return_value = Mock(message=\"fail\")\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            resp = self.challenge.retrieve_challenge_set(\"authz\", \"valid\", \"tok\", False)\n\n        self.assertEqual(resp, [])\n        # Verify the error log message was generated\n        self.assertTrue(\n            any(\n                \"Failed to retrieve challenge set: fail\" in record.message\n                for record in log_context.records\n                if record.levelname == \"ERROR\"\n            )\n        )\n\n    def test_059_challengeset_get_and_parse(self):\n        self.challenge.retrieve_challenge_set = Mock(return_value=[{\"foo\": \"bar\"}])\n        self.assertEqual(\n            self.challenge.challengeset_get(\"a\", \"b\", \"c\", False), [{\"foo\": \"bar\"}]\n        )\n        self.challenge.process_challenge_request = Mock(return_value={\"status\": \"ok\"})\n        self.assertEqual(self.challenge.parse(\"content\"), {\"status\": \"ok\"})\n\n    # Additional tests to reach 100% coverage\n\n    def test_060_context_manager(self):\n        \"\"\"Test context manager functionality\"\"\"\n        with patch.object(self.challenge, \"_load_configuration\"):\n            with self.challenge as challenge_instance:\n                self.assertEqual(challenge_instance, self.challenge)\n\n    def test_061_create_challenge_special_types(self):\n        \"\"\"Test create challenge with special challenge types\"\"\"\n        self.challenge.repository = Mock()\n\n        # Test email-reply-00 challenge type\n        request = Mock()\n        request.challenge_type = \"email-reply-00\"\n        request.value = \"test_value\"\n\n        with patch(\n            \"acme_srv.challenge.generate_random_string\", return_value=\"random_token\"\n        ):\n            self.challenge.repository.create_challenge = Mock(return_value=\"chid\")\n            result = self.challenge.repository.create_challenge(request)\n            self.assertEqual(result, \"chid\")\n\n    def test_062_update_challenge_with_all_fields(self):\n        \"\"\"Test challenge update with all optional fields\"\"\"\n        self.challenge.repository = Mock()\n\n        request = Mock()\n        request.challenge_name = \"test_challenge\"\n        request.status = \"valid\"\n        request.source = \"test_source\"\n        request.validated = \"2023-01-01T00:00:00Z\"\n        request.keyauthorization = \"test_keyauth\"\n\n        self.challenge.repository.update_challenge(request)\n        self.challenge.repository.update_challenge.assert_called_once()\n\n    def test_063_get_account_jwk_exception(self):\n        \"\"\"Test get_account_jwk with database exception\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n        # Set up challenge_lookup to return a valid response so jwk_load gets called\n        self.challenge.repository.dbstore.challenge_lookup.return_value = {\n            \"authorization__order__account__name\": \"account_name\"\n        }\n        self.challenge.repository.dbstore.jwk_load.side_effect = Exception(\"DB error\")\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            with self.assertRaises(self.DatabaseError):\n                self.challenge.repository.get_account_jwk(\"account_name\")\n\n        # Verify the critical log message was generated\n        self.assertTrue(\n            any(\n                \"Database error: failed to get account JWK: DB error\" in record.message\n                for record in log_context.records\n                if record.levelname == \"CRITICAL\"\n            )\n        )\n\n    def test_064_get_challengeinfo_by_challengename_none_result(self):\n        \"\"\"Test get_challengeinfo_by_challengename when no challenge found\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n        self.challenge.repository.dbstore.challenge_lookup.return_value = None\n\n        result = self.challenge.repository.get_challengeinfo_by_challengename(\n            \"nonexistent\"\n        )\n        self.assertIsNone(result)\n\n    def test_065_get_challenge_by_name_none_result(self):\n        \"\"\"Test get_challenge_by_name when no challenge found\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n        self.challenge.repository.dbstore.challenge_lookup.return_value = None\n\n        result = self.challenge.repository.get_challenge_by_name(\"nonexistent\")\n        self.assertIsNone(result)\n\n    def test_066_execute_challenge_validation_unsupported_type(self):\n        \"\"\"Test _execute_challenge_validation with unsupported challenge type\"\"\"\n        self.challenge.validator_registry = Mock()\n        self.challenge.validator_registry.is_supported.return_value = False\n        self.challenge.validator_registry.get_supported_types.return_value = [\"dns-01\"]\n\n        self.challenge._get_challenge_validation_details = Mock(\n            return_value={\n                \"type\": \"unsupported-01\",\n                \"token\": \"token\",\n                \"jwk_thumbprint\": \"thumb\",\n                \"keyauthorization\": \"keyauth\",\n                \"authorization_type\": \"dns\",\n                \"authorization_value\": \"example.com\",\n            }\n        )\n\n        with self.assertRaises(self.UnsupportedChallengeTypeError):\n            self.challenge._execute_challenge_validation(\"test_challenge\")\n\n    def test_067_execute_challenge_validation_no_details(self):\n        \"\"\"Test _execute_challenge_validation when details cannot be retrieved\"\"\"\n        self.challenge._get_challenge_validation_details = Mock(return_value=None)\n\n        with self.assertRaises(self.ValidationError):\n            self.challenge._execute_challenge_validation(\"test_challenge\")\n\n    def test_068_extract_challenge_name_from_url_with_suffix(self):\n        \"\"\"Test _extract_challenge_name_from_url with URL suffix\"\"\"\n        self.challenge.path_dic = {\"chall_path\": \"/acme/chall/\"}\n\n        with patch(\n            \"acme_srv.challenge.parse_url\",\n            return_value={\"path\": \"/acme/chall/test_challenge/authz\"},\n        ):\n            result = self.challenge._extract_challenge_name_from_url(\n                \"/acme/chall/test_challenge/authz\"\n            )\n            self.assertEqual(result, \"test_challenge\")\n\n    def test_069_handle_challenge_validation_request_email_address(self):\n        \"\"\"Test challenge validation with email address configuration\"\"\"\n        self.challenge.config.email_identifier_support = True\n        self.challenge.config.email_address = \"test@example.com\"\n\n        info = self.ChallengeInfo(\n            \"c1\",\n            \"email-reply-00\",\n            \"tok\",\n            \"valid\",\n            \"authz\",\n            \"email\",\n            \"val\",\n            \"url\",\n            \"2023-01-01T00:00:00Z\",\n        )\n\n        # Mock the internal method properly\n        self.challenge._validate_tnauthlist_payload = Mock(return_value={\"code\": 200})\n        self.challenge._create_success_response = Mock(\n            return_value={\"status\": \"ok\", \"data\": {\"from\": \"test@example.com\"}}\n        )\n\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {}, {\"url\": \"u\"}, \"c1\", info\n        )\n        self.assertEqual(resp[\"status\"], \"ok\")\n        self.assertEqual(resp[\"data\"][\"from\"], \"test@example.com\")\n\n    def test_070_load_address_check_configuration_deprecated(self):\n        \"\"\"Test loading deprecated source_address_check configuration\"\"\"\n        from configparser import ConfigParser\n\n        config_dic = ConfigParser()\n        config_dic.add_section(\"Challenge\")\n        config_dic.set(\"Challenge\", \"source_address_check\", \"True\")\n\n        # Mock warning to verify it's called\n        with patch.object(self.challenge.logger, \"warning\") as mock_warning:\n            self.challenge._load_address_check_configuration(config_dic)\n            mock_warning.assert_called_once()\n            self.assertTrue(self.challenge.config.forward_address_check)\n\n    def test_071_load_configuration_validation_timeout_error(self):\n        \"\"\"Test loading configuration with invalid validation timeout\"\"\"\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()\n        config_obj.add_section(\"Challenge\")\n        config_obj.set(\"Challenge\", \"challenge_validation_timeout\", \"invalid\")\n\n        with patch(\n            \"acme_srv.challenge.load_config\", return_value=config_obj\n        ), patch.object(self.challenge, \"_load_dns_configuration\"), patch.object(\n            self.challenge, \"_load_proxy_configuration\"\n        ), patch.object(\n            self.challenge, \"_load_address_check_configuration\"\n        ), patch(\n            \"acme_srv.challenge.create_challenge_validator_registry\",\n            return_value=Mock(),\n        ), patch.object(\n            self.challenge, \"_initialize_business_logic_components\"\n        ), patch.object(\n            self.challenge.logger, \"warning\"\n        ) as mock_warning:\n\n            self.challenge._load_configuration()\n            mock_warning.assert_called_once()\n\n    def test_072_load_configuration_email_identifier_no_address(self):\n        \"\"\"Test email identifier support without email address configured\"\"\"\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()\n        config_obj.add_section(\"Order\")\n        config_obj.set(\"Order\", \"email_identifier_support\", \"True\")\n\n        with patch(\n            \"acme_srv.challenge.load_config\", return_value=config_obj\n        ), patch.object(self.challenge, \"_load_dns_configuration\"), patch.object(\n            self.challenge, \"_load_proxy_configuration\"\n        ), patch.object(\n            self.challenge, \"_load_address_check_configuration\"\n        ), patch(\n            \"acme_srv.challenge.create_challenge_validator_registry\",\n            return_value=Mock(),\n        ), patch.object(\n            self.challenge, \"_initialize_business_logic_components\"\n        ), patch.object(\n            self.challenge.logger, \"warning\"\n        ) as mock_warning:\n\n            self.challenge._load_configuration()\n            mock_warning.assert_called_once()\n            self.assertFalse(self.challenge.config.email_identifier_support)\n\n    def test_073_load_configuration_with_url_prefix(self):\n        \"\"\"Test loading configuration with URL prefix\"\"\"\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()\n        config_obj.add_section(\"Directory\")\n        config_obj.set(\"Directory\", \"url_prefix\", \"/custom/prefix\")\n\n        original_path_dic = self.challenge.path_dic.copy()\n\n        with patch(\n            \"acme_srv.challenge.load_config\", return_value=config_obj\n        ), patch.object(self.challenge, \"_load_dns_configuration\"), patch.object(\n            self.challenge, \"_load_proxy_configuration\"\n        ), patch.object(\n            self.challenge, \"_load_address_check_configuration\"\n        ), patch(\n            \"acme_srv.challenge.create_challenge_validator_registry\",\n            return_value=Mock(),\n        ), patch.object(\n            self.challenge, \"_initialize_business_logic_components\"\n        ):\n\n            self.challenge._load_configuration()\n            # Check that URL prefix was applied\n            for key, value in self.challenge.path_dic.items():\n                expected_value = \"/custom/prefix\" + original_path_dic[key]\n                self.assertEqual(value, expected_value)\n\n    # Tests for special challenge creation types\n    def test_074_create_challenge_sectigo_email(self):\n        \"\"\"Test create challenge with sectigo-email-01 type\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n        self.challenge.repository.dbstore.challenge_add.return_value = (\n            \"chid123\"  # db returns an id\n        )\n\n        request = Mock()\n        request.challenge_type = \"sectigo-email-01\"\n        request.value = \"test_value\"\n        request.authorization_name = \"authz1\"\n        request.token = \"token1\"\n\n        with patch(\n            \"acme_srv.challenge.generate_random_string\",\n            return_value=\"random_challenge_name\",\n        ):\n            result = self.challenge.repository.create_challenge(request)\n            self.assertEqual(\n                result, \"random_challenge_name\"\n            )  # method returns challenge name, not chid\n            # Verify that status=5 was set for sectigo-email-01\n            call_args = self.challenge.repository.dbstore.challenge_add.call_args\n            data_dic = call_args[0][2]  # third argument\n            self.assertEqual(data_dic[\"status\"], 5)\n\n    def test_075_create_challenge_email_reply(self):\n        \"\"\"Test create challenge with email-reply-00 type\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n        self.challenge.repository.dbstore.challenge_add.return_value = \"chid456\"\n\n        request = Mock()\n        request.challenge_type = \"email-reply-00\"\n        request.value = \"test_value\"\n        request.authorization_name = \"authz1\"\n        request.token = \"token1\"\n\n        with patch(\"acme_srv.challenge.generate_random_string\") as mock_gen:\n            mock_gen.side_effect = [\n                \"challenge_name\",\n                \"random_token\",\n            ]  # first call for challenge name, second for keyauth\n            result = self.challenge.repository.create_challenge(request)\n            self.assertEqual(result, \"challenge_name\")  # returns challenge name\n            # Verify that keyauthorization was set\n            call_args = self.challenge.repository.dbstore.challenge_add.call_args\n            data_dic = call_args[0][2]  # third argument\n            self.assertEqual(data_dic[\"keyauthorization\"], \"random_token\")\n\n    def test_076_update_challenge_with_individual_fields(self):\n        \"\"\"Test update challenge with different combinations of optional fields\"\"\"\n        self.challenge.repository = self.DatabaseChallengeRepository(\n            Mock(), self.logger\n        )\n\n        # Test with only status\n        request = Mock()\n        request.name = \"test_challenge\"\n        request.status = \"valid\"\n        request.source = None\n        request.validated = None\n        request.keyauthorization = None\n\n        self.challenge.repository.update_challenge(request)\n\n        # Test with only source\n        request.status = None\n        request.source = \"test_source\"\n        self.challenge.repository.update_challenge(request)\n\n        # Test with only validated\n        request.source = None\n        request.validated = \"2023-01-01T00:00:00Z\"\n        self.challenge.repository.update_challenge(request)\n\n        # Test with only keyauthorization\n        request.validated = None\n        request.keyauthorization = \"test_keyauth\"\n        self.challenge.repository.update_challenge(request)\n\n    def test_077_handle_challenge_validation_request_with_validated_flag(self):\n        \"\"\"Test challenge validation response includes validated flag for valid challenges\"\"\"\n        self.challenge.config.email_identifier_support = False\n        self.challenge.config.email_address = None\n\n        info = self.ChallengeInfo(\n            \"c1\",\n            \"dns-01\",\n            \"tok\",\n            \"valid\",\n            \"authz\",\n            \"dns\",\n            \"val\",\n            \"url\",\n            \"2023-01-01T00:00:00Z\",\n        )\n\n        # Create a proper response structure\n        response_data = {\n            \"type\": \"dns-01\",\n            \"status\": \"valid\",\n            \"url\": \"url\",\n            \"token\": \"tok\",\n            \"validated\": \"2023-01-01T00:00:00Z\",\n        }\n\n        self.challenge._validate_tnauthlist_payload = Mock(return_value={\"code\": 200})\n        self.challenge._create_success_response = Mock(\n            return_value={\"status\": \"ok\", \"data\": response_data}\n        )\n\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {}, {\"url\": \"u\"}, \"c1\", info\n        )\n        self.assertEqual(resp[\"status\"], \"ok\")\n        self.assertEqual(resp[\"data\"][\"validated\"], \"2023-01-01T00:00:00Z\")\n\n    def test_078_load_configuration_with_email_identifier_and_address(self):\n        \"\"\"Test loading configuration with email identifier support and valid email address\"\"\"\n        from configparser import ConfigParser\n\n        config_obj = ConfigParser()\n        config_obj.add_section(\"Order\")\n        config_obj.set(\"Order\", \"email_identifier_support\", \"True\")\n        config_obj[\"DEFAULT\"][\n            \"email_address\"\n        ] = \"test@example.com\"  # Use dict-style access for DEFAULT\n\n        with patch(\n            \"acme_srv.challenge.load_config\", return_value=config_obj\n        ), patch.object(self.challenge, \"_load_dns_configuration\"), patch.object(\n            self.challenge, \"_load_proxy_configuration\"\n        ), patch.object(\n            self.challenge, \"_load_address_check_configuration\"\n        ), patch(\n            \"acme_srv.challenge.create_challenge_validator_registry\",\n            return_value=Mock(),\n        ), patch.object(\n            self.challenge, \"_initialize_business_logic_components\"\n        ):\n\n            self.challenge._load_configuration()\n            self.assertTrue(self.challenge.config.email_identifier_support)\n            self.assertEqual(self.challenge.config.email_address, \"test@example.com\")\n\n    # Test for remaining uncovered lines\n    def test_079_initialize_business_logic_components(self):\n        \"\"\"Test _initialize_business_logic_components method\"\"\"\n        with patch(\"acme_srv.challenge.ChallengeFactory\") as mock_factory, patch(\n            \"acme_srv.challenge.ChallengeService\"\n        ) as mock_service:\n            self.challenge.path_dic = {\"chall_path\": \"/chall/\"}\n            self.challenge.config.email_address = \"test@example.com\"\n            self.challenge.repository = Mock()\n            self.challenge.state_manager = Mock()\n            self.challenge.server_name = \"test_server\"\n\n            self.challenge._initialize_business_logic_components()\n\n            mock_factory.assert_called_once()\n            mock_service.assert_called_once()\n\n    # Tests for uncovered lines in process_challenge_request error handling\n    def test_080_process_challenge_request_message_check_failure(self):\n        \"\"\"Test process_challenge_request when message check fails (line 907)\"\"\"\n        # Set up necessary components\n        self.challenge.factory = Mock()\n        self.challenge.service = Mock()\n        self.challenge.message = Mock()\n        # Simulate message check failure\n        self.challenge.message.check.return_value = (\n            400,\n            \"bad request\",\n            \"invalid format\",\n            {},\n            {},\n            \"\",\n        )\n\n        # We need to test that the line is executed, not the return value structure\n        with patch.object(\n            self.challenge, \"_create_error_response\"\n        ) as mock_error_response:\n            mock_error_response.return_value = {\n                \"code\": 400,\n                \"type\": \"bad request\",\n                \"detail\": \"invalid format\",\n            }\n            self.challenge.process_challenge_request(\"invalid_content\")\n            mock_error_response.assert_called_once_with(\n                400, \"bad request\", \"invalid format\"\n            )\n\n    def test_081_process_challenge_request_url_missing_in_protected(self):\n        \"\"\"Test process_challenge_request when URL is missing from protected header (line 910)\"\"\"\n        # Set up necessary components\n        self.challenge.factory = Mock()\n        self.challenge.service = Mock()\n        self.challenge.message = Mock()\n        self.challenge.err_msg_dic = {\"malformed\": \"malformed\"}\n        # Message check succeeds but protected header has no URL\n        self.challenge.message.check.return_value = (\n            200,\n            \"\",\n            \"\",\n            {},\n            {},\n            \"account\",\n        )  # empty protected dict\n\n        # Test that the specific error response line is executed\n        with patch.object(\n            self.challenge, \"_create_error_response\"\n        ) as mock_error_response:\n            mock_error_response.return_value = {\n                \"code\": 400,\n                \"type\": \"malformed\",\n                \"detail\": \"url missing in protected header\",\n            }\n            self.challenge.process_challenge_request(\"content_without_url\")\n            mock_error_response.assert_called_once_with(\n                400, \"malformed\", \"url missing in protected header\"\n            )\n\n    def test_082_process_challenge_request_empty_challenge_name_extraction(self):\n        \"\"\"Test process_challenge_request when challenge name extraction fails (line 918)\"\"\"\n        # Set up necessary components\n        self.challenge.factory = Mock()\n        self.challenge.service = Mock()\n        self.challenge.message = Mock()\n        self.challenge.err_msg_dic = {\"malformed\": \"malformed\"}\n        # Message check succeeds with URL but challenge name extraction fails\n        self.challenge.message.check.return_value = (\n            200,\n            \"\",\n            \"\",\n            {\"url\": \"invalid_url\"},\n            {},\n            \"account\",\n        )\n\n        # Mock the extract method to return empty string (extraction failure)\n        with patch.object(\n            self.challenge, \"_extract_challenge_name_from_url\", return_value=\"\"\n        ), patch.object(\n            self.challenge, \"_create_error_response\"\n        ) as mock_error_response:\n            mock_error_response.return_value = {\n                \"code\": 400,\n                \"type\": \"malformed\",\n                \"detail\": \"could not get challenge\",\n            }\n            self.challenge.process_challenge_request(\"content_with_invalid_url\")\n            mock_error_response.assert_called_once_with(\n                400, \"malformed\", \"could not get challenge\"\n            )\n\n    def test_083_process_challenge_request_nonexistent_challenge_name(self):\n        \"\"\"Test process_challenge_request when challenge doesn't exist in repository (line 924)\"\"\"\n        # Set up necessary components\n        self.challenge.factory = Mock()\n        self.challenge.service = Mock()\n        self.challenge.message = Mock()\n        self.challenge.err_msg_dic = {\"malformed\": \"malformed\"}\n        self.challenge.repository = Mock()\n        # Message check succeeds, URL exists, challenge name extracted but challenge doesn't exist\n        self.challenge.message.check.return_value = (\n            200,\n            \"\",\n            \"\",\n            {\"url\": \"valid_url\"},\n            {},\n            \"account\",\n        )\n        self.challenge.repository.get_challenge_by_name.return_value = (\n            None  # Challenge not found\n        )\n\n        # Mock the extract method to return valid challenge name\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"nonexistent_challenge\",\n        ), patch.object(\n            self.challenge, \"_create_error_response\"\n        ) as mock_error_response:\n            mock_error_response.return_value = {\n                \"code\": 400,\n                \"type\": \"malformed\",\n                \"detail\": \"invalid challenge: nonexistent_challenge\",\n            }\n            self.challenge.process_challenge_request(\n                \"content_with_nonexistent_challenge\"\n            )\n            mock_error_response.assert_called_once_with(\n                400, \"malformed\", \"invalid challenge: nonexistent_challenge\"\n            )\n\n    # Tests for the final remaining uncovered lines (389-402, 508, 515)\n    def test_084_execute_challenge_validation_full_context_creation(self):\n        \"\"\"Test _execute_challenge_validation with full ChallengeContext creation (lines 389-402)\"\"\"\n        self.challenge.validator_registry = Mock()\n        self.challenge.validator_registry.is_supported.return_value = True\n\n        # Mock challenge details to trigger ChallengeContext creation\n        challenge_details = {\n            \"type\": \"dns-01\",\n            \"token\": \"test_token\",\n            \"jwk_thumbprint\": \"test_thumbprint\",\n            \"keyauthorization\": \"test_keyauth\",\n            \"authorization_type\": \"dns\",\n            \"authorization_value\": \"example.com\",\n        }\n\n        self.challenge._get_challenge_validation_details = Mock(\n            return_value=challenge_details\n        )\n        self.challenge.config.dns_server_list = [\"8.8.8.8\"]\n        self.challenge.config.proxy_server_list = {\"http\": \"proxy:8080\"}\n        self.challenge.config.validation_timeout = 30\n\n        # Mock the validation retry method to confirm context was created\n        with patch.object(\n            self.challenge, \"_perform_validation_with_retry\"\n        ) as mock_retry:\n            mock_retry.return_value = Mock()  # Return some validation result\n\n            # This will execute lines 389-402 where ChallengeContext is created\n            self.challenge._execute_challenge_validation(\"test_challenge\")\n\n            # Verify that _perform_validation_with_retry was called (which means ChallengeContext was created)\n            mock_retry.assert_called_once()\n            args = mock_retry.call_args[0]\n            self.assertEqual(args[0], \"dns-01\")  # challenge_type\n            # We can't easily verify the context object, but we know it was created if this method was called\n\n    def test_085_handle_challenge_validation_request_email_address_response_building(\n        self,\n    ):\n        \"\"\"Test that line 508 is executed: response_dic[\"data\"][\"from\"] = self.config.email_address\"\"\"\n        # Set up email configuration\n        self.challenge.config.email_address = \"test@example.com\"\n        self.challenge.config.tnauthlist_support = False\n\n        # Create email-reply-00 challenge info\n        info = self.ChallengeInfo(\n            \"c1\", \"email-reply-00\", \"tok\", \"pending\", \"authz\", \"email\", \"val\", \"url\"\n        )\n\n        # Mock dependencies but NOT _create_success_response so we execute the response building logic\n        self.challenge.repository.get_challenge_by_name = Mock(return_value=info)\n        self.challenge._start_async_validation = Mock()\n\n        # Mock _create_success_response to capture what gets passed to it\n        def capture_response_dic(response_dict):\n            return {\"status\": \"ok\", \"captured_data\": response_dict[\"data\"]}\n\n        self.challenge._create_success_response = Mock(side_effect=capture_response_dic)\n\n        # Call the method\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {}, {\"url\": \"test_url\"}, \"c1\", info\n        )\n\n        # Verify line 508 was executed by checking the captured response_dic\n        self.challenge._create_success_response.assert_called_once()\n        captured_data = resp[\"captured_data\"]\n        self.assertEqual(captured_data[\"from\"], \"test@example.com\")\n        self.assertEqual(captured_data[\"type\"], \"email-reply-00\")\n\n    def test_086_handle_challenge_validation_request_validated_flag_response_building(\n        self,\n    ):\n        \"\"\"Test that line 515 is executed: response_dic[\"data\"][\"validated\"] = updated_challenge_info.validated\"\"\"\n        # Set up configuration\n        self.challenge.config.tnauthlist_support = False\n\n        # Create challenge info with validated timestamp and valid status\n        validated_time = \"2023-01-01T12:00:00Z\"\n        info = self.ChallengeInfo(\n            \"c1\", \"dns-01\", \"tok\", \"valid\", \"authz\", \"dns\", \"val\", \"url\", validated_time\n        )\n\n        # Mock dependencies but NOT _create_success_response so we execute the response building logic\n        self.challenge.repository.get_challenge_by_name = Mock(return_value=info)\n        self.challenge._start_async_validation = Mock()\n\n        # Mock _create_success_response to capture what gets passed to it\n        def capture_response_dic(response_dict):\n            return {\"status\": \"ok\", \"captured_data\": response_dict[\"data\"]}\n\n        self.challenge._create_success_response = Mock(side_effect=capture_response_dic)\n\n        # Call the method\n        resp = self.challenge._handle_challenge_validation_request(\n            200, {}, {\"url\": \"test_url\"}, \"c1\", info\n        )\n\n        # Verify line 515 was executed by checking the captured response_dic\n        self.challenge._create_success_response.assert_called_once()\n        captured_data = resp[\"captured_data\"]\n        self.assertEqual(captured_data[\"validated\"], validated_time)\n        self.assertEqual(captured_data[\"status\"], \"valid\")\n\n    def test_087_get_eab_kid_from_challenge_success(self):\n        \"\"\"Test _get_eab_kid_from_challenge with successful EAB kid retrieval\"\"\"\n        self.challenge.repository = Mock()\n        self.challenge.repository.get_challengeinfo_by_challengename.return_value = {\n            \"name\": \"test_challenge\",\n            \"status__name\": \"pending\",\n            \"authorization__order__account__name\": \"account1\",\n            \"authorization__order__account__eab_kid\": \"test_eab_kid_123\",\n        }\n\n        result = self.challenge._get_eab_kid_from_challenge(\"test_challenge\")\n\n        self.assertEqual(result, \"test_eab_kid_123\")\n        self.challenge.repository.get_challengeinfo_by_challengename.assert_called_once_with(\n            \"test_challenge\",\n            vlist=(\n                \"name\",\n                \"status__name\",\n                \"authorization__order__account__name\",\n                \"authorization__order__account__eab_kid\",\n            ),\n        )\n\n    def test_088_get_eab_kid_from_challenge_no_eab_kid(self):\n        \"\"\"Test _get_eab_kid_from_challenge when no EAB kid is found\"\"\"\n        self.challenge.repository = Mock()\n        self.challenge.repository.get_challengeinfo_by_challengename.return_value = {\n            \"name\": \"test_challenge\",\n            \"status__name\": \"pending\",\n            \"authorization__order__account__name\": \"account1\",\n            \"authorization__order__account__eab_kid\": None,\n        }\n\n        result = self.challenge._get_eab_kid_from_challenge(\"test_challenge\")\n\n        self.assertIsNone(result)\n\n    def test_089_get_eab_kid_from_challenge_empty_eab_kid(self):\n        \"\"\"Test _get_eab_kid_from_challenge when EAB kid is empty string\"\"\"\n        self.challenge.repository = Mock()\n        self.challenge.repository.get_challengeinfo_by_challengename.return_value = {\n            \"name\": \"test_challenge\",\n            \"status__name\": \"pending\",\n            \"authorization__order__account__name\": \"account1\",\n            \"authorization__order__account__eab_kid\": \"\",\n        }\n\n        result = self.challenge._get_eab_kid_from_challenge(\"test_challenge\")\n\n        self.assertIsNone(result)\n\n    def test_090_get_eab_kid_from_challenge_missing_key(self):\n        \"\"\"Test _get_eab_kid_from_challenge when EAB kid key is missing\"\"\"\n        self.challenge.repository = Mock()\n        self.challenge.repository.get_challengeinfo_by_challengename.return_value = {\n            \"name\": \"test_challenge\",\n            \"status__name\": \"pending\",\n            \"authorization__order__account__name\": \"account1\"\n            # Missing authorization__order__account__eab_kid key\n        }\n\n        result = self.challenge._get_eab_kid_from_challenge(\"test_challenge\")\n\n        self.assertIsNone(result)\n\n    def test_091_get_eab_kid_from_challenge_exception(self):\n        \"\"\"Test _get_eab_kid_from_challenge with database exception\"\"\"\n        self.challenge.repository = Mock()\n        self.challenge.repository.get_challengeinfo_by_challengename.side_effect = (\n            Exception(\"Database error\")\n        )\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            result = self.challenge._get_eab_kid_from_challenge(\"test_challenge\")\n\n        self.assertIsNone(result)\n        # Verify error log message\n        self.assertTrue(\n            any(\n                \"Failed to get EAB kid from challenge test_challenge: Database error\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"ERROR\"\n            )\n        )\n\n    def test_092_get_challenge_profile_settings_success(self):\n        \"\"\"Test _get_challenge_profile_settings with valid profile\"\"\"\n        profile_dic = {\n            \"test_kid\": {\n                \"challenge\": {\n                    \"challenge_validation_disable\": True,\n                    \"forward_address_check\": True,\n                    \"reverse_address_check\": False,\n                }\n            }\n        }\n\n        result = self.challenge._get_challenge_profile_settings(profile_dic, \"test_kid\")\n\n        expected_settings = {\n            \"challenge_validation_disable\": True,\n            \"forward_address_check\": True,\n            \"reverse_address_check\": False,\n        }\n        self.assertEqual(result, expected_settings)\n\n    def test_093_get_challenge_profile_settings_defaults(self):\n        \"\"\"Test _get_challenge_profile_settings with missing settings using defaults\"\"\"\n        profile_dic = {\"test_kid\": {\"challenge\": {}}}\n\n        result = self.challenge._get_challenge_profile_settings(profile_dic, \"test_kid\")\n\n        expected_settings = {\n            \"challenge_validation_disable\": False,\n            \"forward_address_check\": False,\n            \"reverse_address_check\": False,\n        }\n        self.assertEqual(result, expected_settings)\n\n    def test_094_get_challenge_profile_settings_no_challenge_section(self):\n        \"\"\"Test _get_challenge_profile_settings when challenge section is missing\"\"\"\n        profile_dic = {\"test_kid\": {\"other_section\": {}}}\n\n        result = self.challenge._get_challenge_profile_settings(profile_dic, \"test_kid\")\n\n        expected_settings = {\n            \"challenge_validation_disable\": False,\n            \"forward_address_check\": False,\n            \"reverse_address_check\": False,\n        }\n        self.assertEqual(result, expected_settings)\n\n    def test_095_get_challenge_profile_settings_kid_not_found(self):\n        \"\"\"Test _get_challenge_profile_settings when EAB kid not in profile\"\"\"\n        profile_dic = {\n            \"other_kid\": {\"challenge\": {\"challenge_validation_disable\": True}}\n        }\n\n        result = self.challenge._get_challenge_profile_settings(profile_dic, \"test_kid\")\n\n        self.assertEqual(result, {})\n\n    def test_096_apply_eab_profile_settings_validation_disable(self):\n        \"\"\"Test _apply_eab_profile_settings with validation disable setting\"\"\"\n        settings = {\n            \"challenge_validation_disable\": True,\n            \"forward_address_check\": False,\n            \"reverse_address_check\": False,\n        }\n\n        # Ensure initial state\n        self.challenge.config.validation_disabled = False\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            self.challenge._apply_eab_profile_settings(settings, \"test_kid\")\n\n        self.assertTrue(self.challenge.config.validation_disabled)\n        # Verify info log message\n        self.assertTrue(\n            any(\n                \"Challenge validation is disabled via EAB profiling (eab_kid: test_kid).\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"INFO\"\n            )\n        )\n\n    def test_097_apply_eab_profile_settings_forward_address_check(self):\n        \"\"\"Test _apply_eab_profile_settings with forward address check setting\"\"\"\n        settings = {\n            \"challenge_validation_disable\": False,\n            \"forward_address_check\": True,\n            \"reverse_address_check\": False,\n        }\n\n        # Ensure initial state\n        self.challenge.config.forward_address_check = False\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            self.challenge._apply_eab_profile_settings(settings, \"test_kid\")\n\n        self.assertTrue(self.challenge.config.forward_address_check)\n        # Verify info log message\n        self.assertTrue(\n            any(\n                \"Forward address check is enabled via EAB profiling (eab_kid: test_kid).\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"INFO\"\n            )\n        )\n\n    def test_098_apply_eab_profile_settings_reverse_address_check(self):\n        \"\"\"Test _apply_eab_profile_settings with reverse address check setting\"\"\"\n        settings = {\n            \"challenge_validation_disable\": False,\n            \"forward_address_check\": False,\n            \"reverse_address_check\": True,\n        }\n\n        # Ensure initial state\n        self.challenge.config.reverse_address_check = False\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            self.challenge._apply_eab_profile_settings(settings, \"test_kid\")\n\n        self.assertTrue(self.challenge.config.reverse_address_check)\n        # Verify info log message\n        self.assertTrue(\n            any(\n                \"Reverse address check is enabled via EAB profiling (eab_kid: test_kid).\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"INFO\"\n            )\n        )\n\n    def test_099_apply_eab_profile_settings_all_settings(self):\n        \"\"\"Test _apply_eab_profile_settings with all settings enabled\"\"\"\n        settings = {\n            \"challenge_validation_disable\": True,\n            \"forward_address_check\": True,\n            \"reverse_address_check\": True,\n        }\n\n        # Ensure initial state\n        self.challenge.config.validation_disabled = False\n        self.challenge.config.forward_address_check = False\n        self.challenge.config.reverse_address_check = False\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            self.challenge._apply_eab_profile_settings(settings, \"test_kid\")\n\n        self.assertTrue(self.challenge.config.validation_disabled)\n        self.assertTrue(self.challenge.config.forward_address_check)\n        self.assertTrue(self.challenge.config.reverse_address_check)\n\n        # Verify all three info log messages\n        info_messages = [\n            record.message\n            for record in log_context.records\n            if record.levelname == \"INFO\"\n        ]\n        self.assertIn(\n            \"Challenge validation is disabled via EAB profiling (eab_kid: test_kid).\",\n            info_messages,\n        )\n        self.assertIn(\n            \"Forward address check is enabled via EAB profiling (eab_kid: test_kid).\",\n            info_messages,\n        )\n        self.assertIn(\n            \"Reverse address check is enabled via EAB profiling (eab_kid: test_kid).\",\n            info_messages,\n        )\n\n    def test_100_apply_eab_profile_settings_no_settings(self):\n        \"\"\"Test _apply_eab_profile_settings with no settings enabled\"\"\"\n        settings = {\n            \"challenge_validation_disable\": False,\n            \"forward_address_check\": False,\n            \"reverse_address_check\": False,\n        }\n\n        # Ensure initial state\n        self.challenge.config.validation_disabled = False\n        self.challenge.config.forward_address_check = False\n        self.challenge.config.reverse_address_check = False\n\n        self.challenge._apply_eab_profile_settings(settings, \"test_kid\")\n\n        # Verify nothing changed\n        self.assertFalse(self.challenge.config.validation_disabled)\n        self.assertFalse(self.challenge.config.forward_address_check)\n        self.assertFalse(self.challenge.config.reverse_address_check)\n\n    def test_101_check_challenge_validation_eabprofile_disabled(self):\n        \"\"\"Test _check_challenge_validation_eabprofile when EAB profiling is disabled\"\"\"\n        # Ensure EAB profiling is disabled\n        self.challenge.config.eab_profiling = False\n        self.challenge.config.eab_handler = None\n\n        # Mock the methods that shouldn't be called\n        self.challenge._get_eab_kid_from_challenge = Mock()\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify early return - method should not be called\n        self.challenge._get_eab_kid_from_challenge.assert_not_called()\n\n    def test_102_check_challenge_validation_eabprofile_no_handler(self):\n        \"\"\"Test _check_challenge_validation_eabprofile when EAB handler is None\"\"\"\n        # EAB profiling enabled but no handler\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = None\n\n        # Mock the methods that shouldn't be called\n        self.challenge._get_eab_kid_from_challenge = Mock()\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify early return - method should not be called\n        self.challenge._get_eab_kid_from_challenge.assert_not_called()\n\n    def test_103_check_challenge_validation_eabprofile_no_eab_kid(self):\n        \"\"\"Test _check_challenge_validation_eabprofile when no EAB kid found\"\"\"\n        # Set up EAB profiling\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = Mock()\n\n        # Mock _get_eab_kid_from_challenge to return None\n        self.challenge._get_eab_kid_from_challenge = Mock(return_value=None)\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify _get_eab_kid_from_challenge was called but early return happened\n        self.challenge._get_eab_kid_from_challenge.assert_called_once_with(\n            \"test_challenge\"\n        )\n\n    def test_104_check_challenge_validation_eabprofile_success(self):\n        \"\"\"Test _check_challenge_validation_eabprofile with successful profile application\"\"\"\n        # Set up EAB profiling\n        mock_eab_handler = Mock()\n        mock_eab_handler_instance = Mock()\n        mock_eab_handler.return_value.__enter__ = Mock(\n            return_value=mock_eab_handler_instance\n        )\n        mock_eab_handler.return_value.__exit__ = Mock(return_value=False)\n\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = mock_eab_handler\n\n        # Mock profile data\n        profile_dic = {\n            \"test_kid\": {\n                \"challenge\": {\n                    \"challenge_validation_disable\": True,\n                    \"forward_address_check\": True,\n                }\n            }\n        }\n        mock_eab_handler_instance.key_file_load.return_value = profile_dic\n\n        # Mock methods\n        self.challenge._get_eab_kid_from_challenge = Mock(return_value=\"test_kid\")\n        self.challenge._get_challenge_profile_settings = Mock(\n            return_value={\n                \"challenge_validation_disable\": True,\n                \"forward_address_check\": True,\n                \"reverse_address_check\": False,\n            }\n        )\n        self.challenge._apply_eab_profile_settings = Mock()\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify all methods were called correctly\n        self.challenge._get_eab_kid_from_challenge.assert_called_once_with(\n            \"test_challenge\"\n        )\n        self.challenge._get_challenge_profile_settings.assert_called_once_with(\n            profile_dic, \"test_kid\"\n        )\n        self.challenge._apply_eab_profile_settings.assert_called_once_with(\n            {\n                \"challenge_validation_disable\": True,\n                \"forward_address_check\": True,\n                \"reverse_address_check\": False,\n            },\n            \"test_kid\",\n        )\n\n    def test_105_check_challenge_validation_eabprofile_no_challenge_section(self):\n        \"\"\"Test _check_challenge_validation_eabprofile when profile has no challenge section\"\"\"\n        # Set up EAB profiling\n        mock_eab_handler = Mock()\n        mock_eab_handler_instance = Mock()\n        mock_eab_handler.return_value.__enter__ = Mock(\n            return_value=mock_eab_handler_instance\n        )\n        mock_eab_handler.return_value.__exit__ = Mock(return_value=False)\n\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = mock_eab_handler\n\n        # Mock profile data without challenge section\n        profile_dic = {\"test_kid\": {\"other_section\": {}}}\n        mock_eab_handler_instance.key_file_load.return_value = profile_dic\n\n        # Mock methods\n        self.challenge._get_eab_kid_from_challenge = Mock(return_value=\"test_kid\")\n        self.challenge._get_challenge_profile_settings = Mock()\n        self.challenge._apply_eab_profile_settings = Mock()\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify early return when no challenge section exists\n        self.challenge._get_eab_kid_from_challenge.assert_called_once_with(\n            \"test_challenge\"\n        )\n        self.challenge._get_challenge_profile_settings.assert_not_called()\n        self.challenge._apply_eab_profile_settings.assert_not_called()\n\n    def test_106_check_challenge_validation_eabprofile_kid_not_in_profile(self):\n        \"\"\"Test _check_challenge_validation_eabprofile when EAB kid not in profile\"\"\"\n        # Set up EAB profiling\n        mock_eab_handler = Mock()\n        mock_eab_handler_instance = Mock()\n        mock_eab_handler.return_value.__enter__ = Mock(\n            return_value=mock_eab_handler_instance\n        )\n        mock_eab_handler.return_value.__exit__ = Mock(return_value=False)\n\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = mock_eab_handler\n\n        # Mock profile data with different kid\n        profile_dic = {\n            \"other_kid\": {\"challenge\": {\"challenge_validation_disable\": True}}\n        }\n        mock_eab_handler_instance.key_file_load.return_value = profile_dic\n\n        # Mock methods\n        self.challenge._get_eab_kid_from_challenge = Mock(return_value=\"test_kid\")\n        self.challenge._get_challenge_profile_settings = Mock()\n        self.challenge._apply_eab_profile_settings = Mock()\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify early return when kid not in profile\n        self.challenge._get_eab_kid_from_challenge.assert_called_once_with(\n            \"test_challenge\"\n        )\n        self.challenge._get_challenge_profile_settings.assert_not_called()\n        self.challenge._apply_eab_profile_settings.assert_not_called()\n\n    def test_107_check_challenge_validation_eabprofile_exception(self):\n        \"\"\"Test _check_challenge_validation_eabprofile with exception during processing\"\"\"\n        # Set up EAB profiling\n        mock_eab_handler = Mock()\n        mock_eab_handler_instance = Mock()\n        mock_eab_handler.return_value.__enter__ = Mock(\n            return_value=mock_eab_handler_instance\n        )\n        mock_eab_handler.return_value.__exit__ = Mock(return_value=False)\n\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = mock_eab_handler\n\n        # Mock exception during key_file_load\n        mock_eab_handler_instance.key_file_load.side_effect = Exception(\n            \"EAB handler error\"\n        )\n\n        # Mock methods\n        self.challenge._get_eab_kid_from_challenge = Mock(return_value=\"test_kid\")\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_context:\n            self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Verify error log message\n        self.assertTrue(\n            any(\n                \"Failed to process EAB profile for challenge test_challenge (kid: test_kid): EAB handler error\"\n                in record.message\n                for record in log_context.records\n                if record.levelname == \"ERROR\"\n            )\n        )\n\n    def test_108_check_challenge_validation_eabprofile_exception_during_get_eab_kid(\n        self,\n    ):\n        \"\"\"Test _check_challenge_validation_eabprofile with exception during _get_eab_kid_from_challenge\"\"\"\n        # Set up EAB profiling\n        self.challenge.config.eab_profiling = True\n        self.challenge.config.eab_handler = Mock()\n\n        # Mock _get_eab_kid_from_challenge to raise exception (it handles its own exceptions)\n        self.challenge._get_eab_kid_from_challenge = Mock(\n            return_value=None\n        )  # returns None on exception\n\n        self.challenge._check_challenge_validation_eabprofile(\"test_challenge\")\n\n        # Should handle gracefully and return early\n        self.challenge._get_eab_kid_from_challenge.assert_called_once_with(\n            \"test_challenge\"\n        )\n\n    def test_109_get_challenge_details_success(self):\n        \"\"\"Test get_challenge_details with successful challenge retrieval\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"http-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"test_token\"\n        mock_challenge_info.validated = \"2023-12-01T10:00:00Z\"\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"test_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"http-01\",\n                \"status\": \"pending\",\n                \"token\": \"test_token\",\n                \"validated\": \"2023-12-01T10:00:00Z\",\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_110_get_challenge_details_challenge_not_found(self):\n        \"\"\"Test get_challenge_details when challenge is not found\"\"\"\n        url = \"http://example.com/acme/chall/nonexistent_challenge\"\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"nonexistent_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository, \"get_challenge_by_name\", return_value=None\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\"code\": 404, \"data\": {}}\n        self.assertEqual(result, expected_result)\n\n    def test_111_get_challenge_details_with_none_validated(self):\n        \"\"\"Test get_challenge_details with challenge having None validated field\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"dns-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"dns_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"test_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"dns-01\",\n                \"status\": \"pending\",\n                \"token\": \"dns_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_112_get_challenge_details_valid_status(self):\n        \"\"\"Test get_challenge_details with valid challenge status\"\"\"\n        url = \"http://example.com/acme/chall/valid_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"http-01\"\n        mock_challenge_info.status = \"valid\"\n        mock_challenge_info.token = \"valid_token\"\n        mock_challenge_info.validated = \"2023-12-01T10:00:00Z\"\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"valid_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"http-01\",\n                \"status\": \"valid\",\n                \"token\": \"valid_token\",\n                \"validated\": \"2023-12-01T10:00:00Z\",\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_113_get_challenge_details_invalid_status(self):\n        \"\"\"Test get_challenge_details with invalid challenge status\"\"\"\n        url = \"http://example.com/acme/chall/invalid_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"http-01\"\n        mock_challenge_info.status = \"invalid\"\n        mock_challenge_info.token = \"invalid_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"invalid_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"http-01\",\n                \"status\": \"invalid\",\n                \"token\": \"invalid_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_114_get_challenge_details_processing_status(self):\n        \"\"\"Test get_challenge_details with processing challenge status\"\"\"\n        url = \"http://example.com/acme/chall/processing_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"dns-01\"\n        mock_challenge_info.status = \"processing\"\n        mock_challenge_info.token = \"processing_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"processing_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"dns-01\",\n                \"status\": \"processing\",\n                \"token\": \"processing_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_115_get_challenge_details_tls_alpn_challenge(self):\n        \"\"\"Test get_challenge_details with tls-alpn-01 challenge type\"\"\"\n        url = \"http://example.com/acme/chall/tls_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"tls-alpn-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"tls_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"tls_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"tls-alpn-01\",\n                \"status\": \"pending\",\n                \"token\": \"tls_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_116_get_challenge_details_empty_challenge_name(self):\n        \"\"\"Test get_challenge_details with empty challenge name from URL\"\"\"\n        url = \"http://example.com/acme/chall/\"\n\n        with patch.object(\n            self.challenge, \"_extract_challenge_name_from_url\", return_value=\"\"\n        ):\n            with patch.object(\n                self.challenge.repository, \"get_challenge_by_name\", return_value=None\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\"code\": 404, \"data\": {}}\n        self.assertEqual(result, expected_result)\n\n    def test_117_get_challenge_details_repository_exception(self):\n        \"\"\"Test get_challenge_details with repository exception\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n\n        mock_error_detail = Mock()\n        mock_error_detail.message = \"Database connection failed\"\n        mock_error_response = {\n            \"status\": 500,\n            \"type\": \"urn:ietf:params:acme:error:serverInternal\",\n            \"detail\": \"Database connection failed\",\n        }\n\n        # Set up the existing Mock error_handler\n        self.challenge.error_handler.handle_error.return_value = mock_error_detail\n        self.challenge.error_handler.create_acme_error_response.return_value = (\n            mock_error_response\n        )\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"test_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                side_effect=Exception(\"Database error\"),\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        self.assertEqual(result, mock_error_response)\n        self.challenge.error_handler.handle_error.assert_called_once()\n        self.challenge.error_handler.create_acme_error_response.assert_called_once_with(\n            mock_error_detail, 500\n        )\n\n    def test_118_get_challenge_details_extract_url_exception(self):\n        \"\"\"Test get_challenge_details with exception in URL extraction (not caught by try-catch)\"\"\"\n        url = \"invalid_url\"\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            side_effect=Exception(\"URL parse error\"),\n        ):\n            with self.assertRaises(Exception) as context:\n                self.challenge.get_challenge_details(url)\n\n            self.assertEqual(str(context.exception), \"URL parse error\")\n\n    def test_119_get_challenge_details_special_characters_in_url(self):\n        \"\"\"Test get_challenge_details with special characters in URL\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge_123-abc\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"http-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"special_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"test_challenge_123-abc\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"http-01\",\n                \"status\": \"pending\",\n                \"token\": \"special_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_120_get_challenge_details_long_challenge_name(self):\n        \"\"\"Test get_challenge_details with very long challenge name\"\"\"\n        url = \"http://example.com/acme/chall/very_long_challenge_name_123456789012345678901234567890\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"dns-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"long_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"very_long_challenge_name_123456789012345678901234567890\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                result = self.challenge.get_challenge_details(url)\n\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"dns-01\",\n                \"status\": \"pending\",\n                \"token\": \"long_token\",\n                \"validated\": None,\n            },\n        }\n        self.assertEqual(result, expected_result)\n\n    def test_121_get_challenge_details_logs_debug_message(self):\n        \"\"\"Test get_challenge_details logs appropriate debug message\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        mock_challenge_info = Mock()\n        mock_challenge_info.type = \"http-01\"\n        mock_challenge_info.status = \"pending\"\n        mock_challenge_info.token = \"test_token\"\n        mock_challenge_info.validated = None\n\n        with patch.object(\n            self.challenge,\n            \"_extract_challenge_name_from_url\",\n            return_value=\"test_challenge\",\n        ):\n            with patch.object(\n                self.challenge.repository,\n                \"get_challenge_by_name\",\n                return_value=mock_challenge_info,\n            ):\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                    self.challenge.get_challenge_details(url)\n\n        self.assertIn(\n            \"DEBUG:test_a2c:Challenge.get_challenge_details(test_challenge)\", lcm.output\n        )\n\n    def test_122_perform_validation_with_retry_dns_challenge_success_first_attempt(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry with dns-01 challenge succeeding on first attempt\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = True\n        mock_result.invalid = False\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        result = self.challenge._perform_validation_with_retry(\"dns-01\", mock_context)\n\n        self.assertEqual(result, mock_result)\n        self.challenge.validator_registry.validate_challenge.assert_called_once_with(\n            \"dns-01\", mock_context\n        )\n\n    def test_123_perform_validation_with_retry_dns_challenge_success_after_retries(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry with dns-01 challenge succeeding after retries\"\"\"\n        mock_context = Mock()\n\n        # First two attempts fail, third succeeds\n        mock_result_fail = Mock()\n        mock_result_fail.success = False\n        mock_result_fail.invalid = False\n\n        mock_result_success = Mock()\n        mock_result_success.success = True\n        mock_result_success.invalid = False\n\n        self.challenge.validator_registry.validate_challenge.side_effect = [\n            mock_result_fail,\n            mock_result_fail,\n            mock_result_success,\n        ]\n\n        with patch(\"time.sleep\") as mock_sleep:\n            result = self.challenge._perform_validation_with_retry(\n                \"dns-01\", mock_context\n            )\n\n        self.assertEqual(result, mock_result_success)\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 3\n        )\n        # Should have 2 sleep calls (after first and second attempts)\n        self.assertEqual(mock_sleep.call_count, 2)\n        mock_sleep.assert_called_with(self.challenge.config.dns_validation_pause_timer)\n\n    def test_124_perform_validation_with_retry_dns_challenge_invalid_first_attempt(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry with dns-01 challenge invalid on first attempt\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = False\n        mock_result.invalid = True\n\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        result = self.challenge._perform_validation_with_retry(\"dns-01\", mock_context)\n\n        self.assertEqual(result, mock_result)\n        self.challenge.validator_registry.validate_challenge.assert_called_once_with(\n            \"dns-01\", mock_context\n        )\n\n    def test_125_perform_validation_with_retry_dns_challenge_max_retries_reached(self):\n        \"\"\"Test _perform_validation_with_retry with dns-01 challenge reaching max retries (lines 997-1002)\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = False\n        mock_result.invalid = False\n\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        with patch(\"time.sleep\") as mock_sleep:\n            with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n                result = self.challenge._perform_validation_with_retry(\n                    \"dns-01\", mock_context\n                )\n\n        # Should have called validate_challenge 5 times (max_attempts)\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 5\n        )\n        # Should have 4 sleep calls (after first 4 attempts)\n        self.assertEqual(mock_sleep.call_count, 4)\n        # Result should be marked as invalid after max retries\n        self.assertTrue(result.invalid)\n        # Should log error message\n        self.assertIn(\n            \"ERROR:test_a2c:No more retries left for challenge type dns-01. Invalidating challenge.\",\n            lcm.output,\n        )\n\n    def test_126_perform_validation_with_retry_email_challenge_max_retries_reached(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry with email-reply-00 challenge reaching max retries (lines 997-1002)\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = False\n        mock_result.invalid = False\n\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        with patch(\"time.sleep\") as mock_sleep:\n            with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n                result = self.challenge._perform_validation_with_retry(\n                    \"email-reply-00\", mock_context\n                )\n\n        # Should have called validate_challenge 5 times (max_attempts)\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 5\n        )\n        # Should have 4 sleep calls (after first 4 attempts)\n        self.assertEqual(mock_sleep.call_count, 4)\n        # Result should be marked as invalid after max retries\n        self.assertTrue(result.invalid)\n        # Should log error message\n        self.assertIn(\n            \"ERROR:test_a2c:No more retries left for challenge type email-reply-00. Invalidating challenge.\",\n            lcm.output,\n        )\n\n    def test_127_perform_validation_with_retry_http_challenge_single_attempt(self):\n        \"\"\"Test _perform_validation_with_retry with http-01 challenge (single attempt)\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = False\n        mock_result.invalid = False\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            with patch(\"time.sleep\") as mock_sleep:\n                result = self.challenge._perform_validation_with_retry(\n                    \"http-01\", mock_context\n                )\n        self.assertIn(\n            \"ERROR:test_a2c:No more retries left for challenge type http-01. Invalidating challenge.\",\n            lcm.output,\n        )\n        # Should have called validate_challenge only once (no retries for http-01)\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 1\n        )\n        # No sleep calls for non-retry challenge types\n        mock_sleep.assert_not_called()\n        # Result should be marked as invalid since no retries and it didn't succeed\n        self.assertTrue(result.invalid)\n\n    def test_128_perform_validation_with_retry_tls_challenge_single_attempt(self):\n        \"\"\"Test _perform_validation_with_retry with tls-alpn-01 challenge (single attempt)\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = True\n        mock_result.invalid = False\n\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n\n        result = self.challenge._perform_validation_with_retry(\n            \"tls-alpn-01\", mock_context\n        )\n        # Should have called validate_challenge only once (no retries for tls-alpn-01)\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 1\n        )\n        # Result should be the successful result\n        self.assertEqual(result, mock_result)\n\n    def test_129_perform_validation_with_retry_dns_challenge_fourth_attempt_no_sleep(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry with dns-01 challenge not sleeping on last attempt\"\"\"\n        mock_context = Mock()\n\n        # First 4 attempts fail, 5th doesn't happen due to break logic\n        mock_result_fail = Mock()\n        mock_result_fail.success = False\n        mock_result_fail.invalid = False\n        self.challenge.validator_registry.validate_challenge.return_value = (\n            mock_result_fail\n        )\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            with patch(\"time.sleep\") as mock_sleep:\n                self.challenge._perform_validation_with_retry(\"dns-01\", mock_context)\n        self.assertIn(\n            \"ERROR:test_a2c:No more retries left for challenge type dns-01. Invalidating challenge.\",\n            lcm.output,\n        )\n        # Should have called validate_challenge 5 times\n        self.assertEqual(\n            self.challenge.validator_registry.validate_challenge.call_count, 5\n        )\n        # Should have 4 sleep calls - no sleep after the last attempt\n        self.assertEqual(mock_sleep.call_count, 4)\n\n    def test_130_perform_validation_with_retry_preserves_dns_validation_pause_timer(\n        self,\n    ):\n        \"\"\"Test _perform_validation_with_retry uses correct dns_validation_pause_timer\"\"\"\n        mock_context = Mock()\n        mock_result = Mock()\n        mock_result.success = False\n        mock_result.invalid = False\n        self.challenge.config.dns_validation_pause_timer = 1.5\n        self.challenge.validator_registry.validate_challenge.return_value = mock_result\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            with patch(\"time.sleep\") as mock_sleep:\n                self.challenge._perform_validation_with_retry(\"dns-01\", mock_context)\n        self.assertIn(\n            \"ERROR:test_a2c:No more retries left for challenge type dns-01. Invalidating challenge.\",\n            lcm.output,\n        )\n        # Verify sleep was called with the configured timer value\n        mock_sleep.assert_called_with(1.5)\n\n    def test_131_get_legacy_api_calls_get_challenge_details(self):\n        \"\"\"Test get() legacy API method calls get_challenge_details\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        expected_result = {\"code\": 200, \"data\": {\"type\": \"http-01\"}}\n\n        with patch.object(\n            self.challenge, \"get_challenge_details\", return_value=expected_result\n        ) as mock_get_details:\n            result = self.challenge.get(url)\n\n        # Should call get_challenge_details with the same URL\n        mock_get_details.assert_called_once_with(url)\n        # Should return the same result\n        self.assertEqual(result, expected_result)\n\n    def test_132_get_legacy_api_logs_debug_message(self):\n        \"\"\"Test get() legacy API method logs appropriate debug message\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        with patch.object(\n            self.challenge, \"get_challenge_details\", return_value={\"code\": 200}\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n                self.challenge.get(url)\n\n        # Should log debug message\n        self.assertIn(\"DEBUG:test_a2c:Challenge.get() called - legacy API\", lcm.output)\n\n    def test_133_get_legacy_api_handles_404_response(self):\n        \"\"\"Test get() legacy API method handles 404 response from get_challenge_details\"\"\"\n        url = \"http://example.com/acme/chall/nonexistent_challenge\"\n        expected_result = {\"code\": 404, \"data\": {}}\n\n        with patch.object(\n            self.challenge, \"get_challenge_details\", return_value=expected_result\n        ) as mock_get_details:\n            result = self.challenge.get(url)\n\n        mock_get_details.assert_called_once_with(url)\n        self.assertEqual(result, expected_result)\n\n    def test_134_get_legacy_api_handles_error_response(self):\n        \"\"\"Test get() legacy API method handles error response from get_challenge_details\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n        expected_result = {\n            \"status\": 500,\n            \"type\": \"urn:ietf:params:acme:error:serverInternal\",\n            \"detail\": \"Database error\",\n        }\n\n        with patch.object(\n            self.challenge, \"get_challenge_details\", return_value=expected_result\n        ) as mock_get_details:\n            result = self.challenge.get(url)\n\n        mock_get_details.assert_called_once_with(url)\n        self.assertEqual(result, expected_result)\n\n    def test_135_get_legacy_api_passes_through_all_response_types(self):\n        \"\"\"Test get() legacy API method passes through various response types\"\"\"\n        url = \"http://example.com/acme/chall/test_challenge\"\n\n        # Test with complex response\n        expected_result = {\n            \"code\": 200,\n            \"data\": {\n                \"type\": \"dns-01\",\n                \"status\": \"valid\",\n                \"token\": \"complex_token_123\",\n                \"validated\": \"2023-12-01T10:00:00Z\",\n            },\n        }\n\n        with patch.object(\n            self.challenge, \"get_challenge_details\", return_value=expected_result\n        ) as mock_get_details:\n            result = self.challenge.get(url)\n\n        mock_get_details.assert_called_once_with(url)\n        self.assertEqual(result, expected_result)\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_136_start_async_validation_sync_mode(self, mock_thread_class):\n        \"\"\"Test _start_async_validation with sync mode (async_mode=False)\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge.config.async_mode = False\n        self.challenge.config.validation_timeout = 30\n        self.challenge._perform_challenge_validation = Mock()\n        challenge_name = \"test_challenge\"\n        payload = {\"key\": \"value\"}\n\n        # Execute\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log:\n            self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify thread creation and execution\n        mock_thread_class.assert_called_once_with(\n            target=self.challenge._perform_challenge_validation,\n            args=(challenge_name, payload),\n        )\n        mock_thread.start.assert_called_once()\n        mock_thread.join.assert_called_once_with(timeout=30)\n\n        # Verify logging\n        self.assertTrue(\n            any(\n                \"Challenge._start_async_validation(test_challenge)\" in message\n                for message in log.output\n            )\n        )\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_137_start_async_validation_async_mode(self, mock_thread_class):\n        \"\"\"Test _start_async_validation with async mode (async_mode=True)\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge.config.async_mode = True\n        self.challenge.config.validation_timeout = 30\n        self.challenge._perform_challenge_validation = Mock()\n        challenge_name = \"async_challenge\"\n        payload = {\"test\": \"data\"}\n\n        # Execute\n        with self.assertLogs(self.logger, level=\"INFO\") as log:\n            self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify thread creation and execution\n        mock_thread_class.assert_called_once_with(\n            target=self.challenge._perform_challenge_validation,\n            args=(challenge_name, payload),\n        )\n        mock_thread.start.assert_called_once()\n        # In async mode, join should NOT be called\n        mock_thread.join.assert_not_called()\n\n        # Verify async logging\n        self.assertTrue(\n            any(\n                \"asynchronous Challenge validation enabled, not waiting for result\"\n                in message\n                for message in log.output\n            )\n        )\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_138_start_async_validation_empty_payload(self, mock_thread_class):\n        \"\"\"Test _start_async_validation with empty payload\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge.config.async_mode = False\n        self.challenge.config.validation_timeout = 15\n        self.challenge._perform_challenge_validation = Mock()\n        challenge_name = \"empty_payload_challenge\"\n        payload = {}\n\n        # Execute\n        self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify thread creation with empty payload\n        mock_thread_class.assert_called_once_with(\n            target=self.challenge._perform_challenge_validation,\n            args=(challenge_name, payload),\n        )\n        mock_thread.start.assert_called_once()\n        mock_thread.join.assert_called_once_with(timeout=15)\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_139_start_async_validation_complex_payload(self, mock_thread_class):\n        \"\"\"Test _start_async_validation with complex payload data\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge.config.async_mode = True\n        self.challenge._perform_challenge_validation = Mock()\n        challenge_name = \"complex_challenge\"\n        payload = {\n            \"protected\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\",\n            \"signature\": \"signature_value_here\",\n            \"atc\": \"token_value\",\n            \"nested\": {\"data\": {\"value\": 123}},\n        }\n\n        # Execute\n        self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify thread creation with complex payload\n        mock_thread_class.assert_called_once_with(\n            target=self.challenge._perform_challenge_validation,\n            args=(challenge_name, payload),\n        )\n        mock_thread.start.assert_called_once()\n        mock_thread.join.assert_not_called()  # async mode\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_140_start_async_validation_different_timeout_values(\n        self, mock_thread_class\n    ):\n        \"\"\"Test _start_async_validation with different timeout values in sync mode\"\"\"\n        test_cases = [1, 5, 10, 60, 120]\n\n        for timeout in test_cases:\n            with self.subTest(timeout=timeout):\n                # Reset mock\n                mock_thread_class.reset_mock()\n                mock_thread = Mock()\n                mock_thread_class.return_value = mock_thread\n\n                # Setup\n                self.challenge.config.async_mode = False\n                self.challenge.config.validation_timeout = timeout\n                self.challenge._perform_challenge_validation = Mock()\n                challenge_name = f\"timeout_test_{timeout}\"\n                payload = {\"timeout_test\": timeout}\n\n                # Execute\n                self.challenge._start_async_validation(challenge_name, payload)\n\n                # Verify correct timeout is used\n                mock_thread.join.assert_called_once_with(timeout=timeout)\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_141_start_async_validation_thread_target_arguments(\n        self, mock_thread_class\n    ):\n        \"\"\"Test _start_async_validation passes correct arguments to thread target\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge.config.async_mode = False\n        self.challenge._perform_challenge_validation = Mock()\n\n        # Test with various argument combinations\n        test_cases = [\n            (\"challenge1\", {}),\n            (\"challenge_with_data\", {\"key1\": \"value1\"}),\n            (\"complex_name_123\", {\"multiple\": \"values\", \"nested\": {\"data\": True}}),\n            (\"\", {\"empty_name_test\": True}),\n        ]\n\n        for challenge_name, payload in test_cases:\n            with self.subTest(challenge_name=challenge_name, payload=payload):\n                # Reset mock\n                mock_thread_class.reset_mock()\n                mock_thread = Mock()\n                mock_thread_class.return_value = mock_thread\n\n                # Execute\n                self.challenge._start_async_validation(challenge_name, payload)\n\n                # Verify thread target and arguments\n                mock_thread_class.assert_called_once_with(\n                    target=self.challenge._perform_challenge_validation,\n                    args=(challenge_name, payload),\n                )\n\n    @patch(\"acme_srv.challenge.Thread\")\n    def test_142_start_async_validation_logging_behavior(self, mock_thread_class):\n        \"\"\"Test _start_async_validation logging in both sync and async modes\"\"\"\n        # Setup\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n        self.challenge._perform_challenge_validation = Mock()\n        challenge_name = \"logging_test_challenge\"\n        payload = {\"logging\": \"test\"}\n\n        # Test sync mode logging\n        self.challenge.config.async_mode = False\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log_sync:\n            self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify sync mode only has debug logging\n        debug_messages = [msg for msg in log_sync.output if \"DEBUG\" in msg]\n        info_messages = [\n            msg\n            for msg in log_sync.output\n            if \"asynchronous Challenge validation enabled\" in msg\n        ]\n        self.assertGreater(len(debug_messages), 0)\n        self.assertEqual(len(info_messages), 0)\n\n        # Reset mock for async test\n        mock_thread_class.reset_mock()\n        mock_thread = Mock()\n        mock_thread_class.return_value = mock_thread\n\n        # Test async mode logging\n        self.challenge.config.async_mode = True\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log_async:\n            self.challenge._start_async_validation(challenge_name, payload)\n\n        # Verify async mode has both debug and info logging\n        debug_messages = [msg for msg in log_async.output if \"DEBUG\" in msg]\n        info_messages = [\n            msg\n            for msg in log_async.output\n            if \"asynchronous Challenge validation enabled\" in msg\n        ]\n        self.assertGreater(len(debug_messages), 0)\n        self.assertGreater(len(info_messages), 0)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_challenge_business_logic.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unit tests for challenge_business_logic.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport logging\nimport json\nfrom unittest.mock import Mock, patch\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestChallengeInfo(unittest.TestCase):\n    \"\"\"Test cases for ChallengeInfo dataclass\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeInfo\n\n        self.ChallengeInfo = ChallengeInfo\n\n    def test_001_challenge_info_creation_basic(self):\n        \"\"\"Test basic ChallengeInfo creation\"\"\"\n        challenge = self.ChallengeInfo(\n            name=\"test-challenge\",\n            type=\"http-01\",\n            token=\"test-token\",\n            status=\"pending\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n        )\n\n        self.assertEqual(challenge.name, \"test-challenge\")\n        self.assertEqual(challenge.type, \"http-01\")\n        self.assertEqual(challenge.token, \"test-token\")\n        self.assertEqual(challenge.status, \"pending\")\n        self.assertEqual(challenge.authorization_name, \"test-auth\")\n        self.assertEqual(challenge.authorization_type, \"dns\")\n        self.assertEqual(challenge.authorization_value, \"example.com\")\n        self.assertEqual(challenge.url, \"http://example.com/challenge\")\n        self.assertIsNone(challenge.validated)\n\n    def test_002_challenge_info_creation_with_validated(self):\n        \"\"\"Test ChallengeInfo creation with validated timestamp\"\"\"\n        challenge = self.ChallengeInfo(\n            name=\"test-challenge\",\n            type=\"dns-01\",\n            token=\"test-token\",\n            status=\"valid\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n            validated=\"2023-01-01T00:00:00Z\",\n        )\n\n        self.assertEqual(challenge.validated, \"2023-01-01T00:00:00Z\")\n        self.assertEqual(challenge.status, \"valid\")\n\n    def test_003_challenge_info_equality(self):\n        \"\"\"Test ChallengeInfo equality comparison\"\"\"\n        challenge1 = self.ChallengeInfo(\n            name=\"test-challenge\",\n            type=\"http-01\",\n            token=\"test-token\",\n            status=\"pending\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n        )\n\n        challenge2 = self.ChallengeInfo(\n            name=\"test-challenge\",\n            type=\"http-01\",\n            token=\"test-token\",\n            status=\"pending\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n        )\n\n        self.assertEqual(challenge1, challenge2)\n\n    def test_004_challenge_info_inequality(self):\n        \"\"\"Test ChallengeInfo inequality comparison\"\"\"\n        challenge1 = self.ChallengeInfo(\n            name=\"test-challenge-1\",\n            type=\"http-01\",\n            token=\"test-token\",\n            status=\"pending\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n        )\n\n        challenge2 = self.ChallengeInfo(\n            name=\"test-challenge-2\",\n            type=\"http-01\",\n            token=\"test-token\",\n            status=\"pending\",\n            authorization_name=\"test-auth\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            url=\"http://example.com/challenge\",\n        )\n\n        self.assertNotEqual(challenge1, challenge2)\n\n\nclass TestChallengeCreationRequest(unittest.TestCase):\n    \"\"\"Test cases for ChallengeCreationRequest dataclass\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeCreationRequest\n\n        self.ChallengeCreationRequest = ChallengeCreationRequest\n\n    def test_005_creation_request_basic(self):\n        \"\"\"Test basic ChallengeCreationRequest creation\"\"\"\n        request = self.ChallengeCreationRequest(\n            authorization_name=\"test-auth\", challenge_type=\"http-01\", token=\"test-token\"\n        )\n\n        self.assertEqual(request.authorization_name, \"test-auth\")\n        self.assertEqual(request.challenge_type, \"http-01\")\n        self.assertEqual(request.token, \"test-token\")\n        self.assertIsNone(request.value)\n        self.assertEqual(request.expiry, 3600)  # default value\n\n    def test_006_creation_request_with_value(self):\n        \"\"\"Test ChallengeCreationRequest with value\"\"\"\n        request = self.ChallengeCreationRequest(\n            authorization_name=\"test-auth\",\n            challenge_type=\"dns-01\",\n            token=\"test-token\",\n            value=\"example.com\",\n        )\n\n        self.assertEqual(request.value, \"example.com\")\n\n    def test_007_creation_request_custom_expiry(self):\n        \"\"\"Test ChallengeCreationRequest with custom expiry\"\"\"\n        request = self.ChallengeCreationRequest(\n            authorization_name=\"test-auth\",\n            challenge_type=\"http-01\",\n            token=\"test-token\",\n            expiry=7200,\n        )\n\n        self.assertEqual(request.expiry, 7200)\n\n    def test_008_creation_request_email_challenge(self):\n        \"\"\"Test ChallengeCreationRequest for email challenge\"\"\"\n        request = self.ChallengeCreationRequest(\n            authorization_name=\"test-auth\",\n            challenge_type=\"email-reply-00\",\n            token=\"test-token\",\n            value=\"user@example.com\",\n            expiry=1800,\n        )\n\n        self.assertEqual(request.challenge_type, \"email-reply-00\")\n        self.assertEqual(request.value, \"user@example.com\")\n        self.assertEqual(request.expiry, 1800)\n\n\nclass TestChallengeUpdateRequest(unittest.TestCase):\n    \"\"\"Test cases for ChallengeUpdateRequest dataclass\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeUpdateRequest\n\n        self.ChallengeUpdateRequest = ChallengeUpdateRequest\n\n    def test_009_update_request_basic(self):\n        \"\"\"Test basic ChallengeUpdateRequest creation\"\"\"\n        request = self.ChallengeUpdateRequest(name=\"test-challenge\")\n\n        self.assertEqual(request.name, \"test-challenge\")\n        self.assertIsNone(request.status)\n        self.assertIsNone(request.source)\n        self.assertIsNone(request.validated)\n        self.assertIsNone(request.keyauthorization)\n\n    def test_010_update_request_status_only(self):\n        \"\"\"Test ChallengeUpdateRequest with status update\"\"\"\n        request = self.ChallengeUpdateRequest(\n            name=\"test-challenge\", status=\"processing\"\n        )\n\n        self.assertEqual(request.name, \"test-challenge\")\n        self.assertEqual(request.status, \"processing\")\n\n    def test_011_update_request_full(self):\n        \"\"\"Test ChallengeUpdateRequest with all fields\"\"\"\n        request = self.ChallengeUpdateRequest(\n            name=\"test-challenge\",\n            status=\"valid\",\n            source=\"192.168.1.1\",\n            validated=1640995200,\n            keyauthorization=\"test-key-auth\",\n        )\n\n        self.assertEqual(request.name, \"test-challenge\")\n        self.assertEqual(request.status, \"valid\")\n        self.assertEqual(request.source, \"192.168.1.1\")\n        self.assertEqual(request.validated, 1640995200)\n        self.assertEqual(request.keyauthorization, \"test-key-auth\")\n\n    def test_012_update_request_partial(self):\n        \"\"\"Test ChallengeUpdateRequest with partial updates\"\"\"\n        request = self.ChallengeUpdateRequest(\n            name=\"test-challenge\", status=\"invalid\", source=\"192.168.1.100\"\n        )\n\n        self.assertEqual(request.status, \"invalid\")\n        self.assertEqual(request.source, \"192.168.1.100\")\n        self.assertIsNone(request.validated)\n        self.assertIsNone(request.keyauthorization)\n\n\nclass MockChallengeRepository:\n    \"\"\"Mock implementation of ChallengeRepository for testing\"\"\"\n\n    def __init__(self):\n        self.challenges = {}\n        self.authorizations = {}\n        self.call_log = []\n\n    def find_challenges_by_authorization(self, authorization_name: str):\n        self.call_log.append((\"find_challenges_by_authorization\", authorization_name))\n        return self.challenges.get(authorization_name, [])\n\n    def get_challenge_by_name(self, name: str):\n        self.call_log.append((\"get_challenge_by_name\", name))\n        for challenges in self.challenges.values():\n            for challenge in challenges:\n                if challenge.name == name:\n                    return challenge\n        return None\n\n    def get_challengeinfo_by_challengename(self, name: str, vlist=None):\n        self.call_log.append((\"get_challengeinfo_by_challengename\", name, vlist))\n        # Mock response for email challenge\n        if name == \"email-challenge-1\":\n            return {\n                \"name\": name,\n                \"keyauthorization\": \"test-key-auth\",\n                \"authorization__value\": \"test@example.com\",\n            }\n        return None\n\n    def create_challenge(self, request):\n        self.call_log.append((\"create_challenge\", request))\n        challenge_name = f\"{request.challenge_type}-{request.authorization_name}-1\"\n        return challenge_name\n\n    def update_challenge(self, request):\n        self.call_log.append((\"update_challenge\", request))\n        return True\n\n    def update_authorization_status(self, challenge_name: str, status: str):\n        self.call_log.append((\"update_authorization_status\", challenge_name, status))\n        return True\n\n    def get_account_jwk(self, challenge_name: str):\n        self.call_log.append((\"get_account_jwk\", challenge_name))\n        return {\"kty\": \"RSA\", \"n\": \"test\", \"e\": \"AQAB\"}\n\n\nclass TestChallengeStateManager(unittest.TestCase):\n    \"\"\"Test cases for ChallengeStateManager\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeStateManager\n\n        self.logger = Mock(spec=logging.Logger)\n        self.repository = MockChallengeRepository()\n        self.state_manager = ChallengeStateManager(self.repository, self.logger)\n\n    def test_013_state_manager_initialization(self):\n        \"\"\"Test ChallengeStateManager initialization\"\"\"\n        self.assertEqual(self.state_manager.repository, self.repository)\n        self.assertEqual(self.state_manager.logger, self.logger)\n\n    def test_014_transition_to_processing_success(self):\n        \"\"\"Test successful transition to processing state\"\"\"\n        result = self.state_manager.transition_to_processing(\"test-challenge\")\n\n        self.assertTrue(result)\n        self.logger.debug.assert_called()\n\n        # Check that repository was called correctly\n        calls = self.repository.call_log\n        self.assertEqual(len(calls), 1)\n        self.assertEqual(calls[0][0], \"update_challenge\")\n        update_request = calls[0][1]\n        self.assertEqual(update_request.name, \"test-challenge\")\n        self.assertEqual(update_request.status, \"processing\")\n\n    def test_015_transition_to_processing_failure(self):\n        \"\"\"Test failed transition to processing state\"\"\"\n        # Mock repository to return False\n        self.repository.update_challenge = Mock(return_value=False)\n\n        result = self.state_manager.transition_to_processing(\"test-challenge\")\n\n        self.assertFalse(result)\n\n    def test_016_transition_to_valid_success(self):\n        \"\"\"Test successful transition to valid state\"\"\"\n        result = self.state_manager.transition_to_valid(\n            \"test-challenge\",\n            source_address=\"192.168.1.1\",\n            validated_timestamp=1640995200,\n        )\n\n        self.assertTrue(result)\n\n        # Check repository calls\n        calls = self.repository.call_log\n        self.assertEqual(len(calls), 2)\n\n        # First call should update challenge\n        self.assertEqual(calls[0][0], \"update_challenge\")\n        update_request = calls[0][1]\n        self.assertEqual(update_request.name, \"test-challenge\")\n        self.assertEqual(update_request.status, \"valid\")\n        self.assertEqual(update_request.source, \"192.168.1.1\")\n        self.assertEqual(update_request.validated, 1640995200)\n\n        # Second call should update authorization\n        self.assertEqual(calls[1][0], \"update_authorization_status\")\n        self.assertEqual(calls[1][1], \"test-challenge\")\n        self.assertEqual(calls[1][2], \"valid\")\n\n    def test_017_transition_to_valid_with_defaults(self):\n        \"\"\"Test transition to valid state with default parameters\"\"\"\n        result = self.state_manager.transition_to_valid(\"test-challenge\")\n\n        self.assertTrue(result)\n\n        update_request = self.repository.call_log[0][1]\n        self.assertEqual(update_request.name, \"test-challenge\")\n        self.assertEqual(update_request.status, \"valid\")\n        self.assertIsNone(update_request.source)\n        self.assertIsNone(update_request.validated)\n\n    def test_018_transition_to_valid_challenge_update_failure(self):\n        \"\"\"Test transition to valid fails on challenge update\"\"\"\n        self.repository.update_challenge = Mock(return_value=False)\n\n        result = self.state_manager.transition_to_valid(\"test-challenge\")\n\n        self.assertFalse(result)\n        # Authorization update should not be called if challenge update fails\n        self.repository.update_challenge.assert_called_once()\n\n    def test_019_transition_to_valid_authorization_update_failure(self):\n        \"\"\"Test transition to valid fails on authorization update\"\"\"\n        self.repository.update_authorization_status = Mock(return_value=False)\n\n        result = self.state_manager.transition_to_valid(\"test-challenge\")\n\n        self.assertFalse(result)\n\n    def test_020_transition_to_invalid_success(self):\n        \"\"\"Test successful transition to invalid state\"\"\"\n        result = self.state_manager.transition_to_invalid(\n            \"test-challenge\", source_address=\"192.168.1.100\"\n        )\n\n        self.assertTrue(result)\n\n        calls = self.repository.call_log\n        self.assertEqual(len(calls), 2)\n\n        # Check challenge update\n        update_request = calls[0][1]\n        self.assertEqual(update_request.name, \"test-challenge\")\n        self.assertEqual(update_request.status, \"invalid\")\n        self.assertEqual(update_request.source, \"192.168.1.100\")\n\n        # Check authorization update\n        self.assertEqual(calls[1][0], \"update_authorization_status\")\n        self.assertEqual(calls[1][2], \"invalid\")\n\n    def test_021_transition_to_invalid_with_defaults(self):\n        \"\"\"Test transition to invalid state with default parameters\"\"\"\n        result = self.state_manager.transition_to_invalid(\"test-challenge\")\n\n        self.assertTrue(result)\n\n        update_request = self.repository.call_log[0][1]\n        self.assertIsNone(update_request.source)\n\n    def test_022_transition_to_invalid_challenge_failure(self):\n        \"\"\"Test transition to invalid fails on challenge update\"\"\"\n        self.repository.update_challenge = Mock(return_value=False)\n\n        result = self.state_manager.transition_to_invalid(\"test-challenge\")\n\n        self.assertFalse(result)\n\n    def test_023_transition_to_invalid_authorization_failure(self):\n        \"\"\"Test transition to invalid fails on authorization update\"\"\"\n        self.repository.update_authorization_status = Mock(return_value=False)\n\n        result = self.state_manager.transition_to_invalid(\"test-challenge\")\n\n        self.assertFalse(result)\n\n    def test_024_update_key_authorization_success(self):\n        \"\"\"Test successful key authorization update\"\"\"\n        result = self.state_manager.update_key_authorization(\n            \"test-challenge\", \"test-key-auth\"\n        )\n\n        self.assertTrue(result)\n\n        calls = self.repository.call_log\n        self.assertEqual(len(calls), 1)\n\n        update_request = calls[0][1]\n        self.assertEqual(update_request.name, \"test-challenge\")\n        self.assertEqual(update_request.keyauthorization, \"test-key-auth\")\n        self.assertIsNone(update_request.status)  # Other fields should be None\n\n    def test_025_update_key_authorization_failure(self):\n        \"\"\"Test failed key authorization update\"\"\"\n        self.repository.update_challenge = Mock(return_value=False)\n\n        result = self.state_manager.update_key_authorization(\n            \"test-challenge\", \"test-key-auth\"\n        )\n\n        self.assertFalse(result)\n\n    def test_026_update_key_authorization_empty_string(self):\n        \"\"\"Test key authorization update with empty string\"\"\"\n        result = self.state_manager.update_key_authorization(\"test-challenge\", \"\")\n\n        self.assertTrue(result)\n        update_request = self.repository.call_log[0][1]\n        self.assertEqual(update_request.keyauthorization, \"\")\n\n    def test_027_logger_debug_calls(self):\n        \"\"\"Test that logger.debug is called appropriately\"\"\"\n        self.state_manager.transition_to_processing(\"test-challenge\")\n\n        # Check that debug was called for method entry and exit\n        self.assertGreaterEqual(self.logger.debug.call_count, 2)\n        debug_calls = self.logger.debug.call_args_list\n\n        # First call should be method entry\n        self.assertIn(\"transition_to_processing\", str(debug_calls[0]))\n\n        # Last call should be method exit with result\n        self.assertIn(\"transition_to_processing\", str(debug_calls[-1]))\n\n    def test_028_transition_with_none_challenge_name(self):\n        \"\"\"Test transitions with None challenge name\"\"\"\n        result = self.state_manager.transition_to_processing(None)\n        self.assertTrue(result)  # Repository mock returns True for any input\n\n    def test_029_transition_with_empty_challenge_name(self):\n        \"\"\"Test transitions with empty challenge name\"\"\"\n        result = self.state_manager.transition_to_processing(\"\")\n        self.assertTrue(result)\n\n    def test_030_update_key_authorization_with_none(self):\n        \"\"\"Test key authorization update with None value\"\"\"\n        result = self.state_manager.update_key_authorization(\"test-challenge\", None)\n        self.assertTrue(result)\n\n        update_request = self.repository.call_log[-1][1]\n        self.assertIsNone(update_request.keyauthorization)\n\n    def test_031_state_manager_repository_exception_handling(self):\n        \"\"\"Test state manager behavior when repository raises exceptions\"\"\"\n        self.repository.update_challenge = Mock(side_effect=Exception(\"Database error\"))\n\n        # The implementation doesn't handle exceptions, so this will raise\n        with self.assertRaises(Exception):\n            self.state_manager.transition_to_processing(\"test-challenge\")\n\n    def test_032_transition_to_valid_large_timestamp(self):\n        \"\"\"Test transition to valid with large timestamp\"\"\"\n        large_timestamp = 9999999999  # Year 2286\n\n        result = self.state_manager.transition_to_valid(\n            \"test-challenge\", validated_timestamp=large_timestamp\n        )\n\n        self.assertTrue(result)\n        update_request = self.repository.call_log[0][1]\n        self.assertEqual(update_request.validated, large_timestamp)\n\n\nclass TestChallengeFactory(unittest.TestCase):\n    \"\"\"Test cases for ChallengeFactory\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeFactory\n\n        self.logger = Mock(spec=logging.Logger)\n        self.repository = MockChallengeRepository()\n        self.factory = ChallengeFactory(\n            repository=self.repository,\n            logger=self.logger,\n            server_name=\"https://example.com\",\n            challenge_path=\"/acme/chall/\",\n            email_address=\"admin@example.com\",\n        )\n\n    def test_033_factory_initialization(self):\n        \"\"\"Test ChallengeFactory initialization\"\"\"\n        self.assertEqual(self.factory.repository, self.repository)\n        self.assertEqual(self.factory.logger, self.logger)\n        self.assertEqual(self.factory.server_name, \"https://example.com\")\n        self.assertEqual(self.factory.challenge_path, \"/acme/chall/\")\n        self.assertEqual(self.factory.email_address, \"admin@example.com\")\n\n    def test_034_create_standard_challenge_set_dns_identifier(self):\n        \"\"\"Test creating standard challenge set for DNS identifier\"\"\"\n        challenges = self.factory.create_standard_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            value=\"example.com\",\n        )\n\n        self.assertEqual(len(challenges), 3)  # http-01, dns-01, tls-alpn-01\n\n        types = [c[\"type\"] for c in challenges]\n        self.assertIn(\"http-01\", types)\n        self.assertIn(\"dns-01\", types)\n        self.assertIn(\"tls-alpn-01\", types)\n\n        # Check common properties\n        for challenge in challenges:\n            self.assertEqual(challenge[\"token\"], \"test-token\")\n            self.assertEqual(challenge[\"status\"], \"pending\")\n            self.assertTrue(\n                challenge[\"url\"].startswith(\"https://example.com/acme/chall/\")\n            )\n\n    def test_035_create_standard_challenge_set_ip_identifier(self):\n        \"\"\"Test creating standard challenge set for IP identifier (no DNS)\"\"\"\n        challenges = self.factory.create_standard_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"ip\",\n            value=\"192.168.1.1\",\n        )\n\n        self.assertEqual(len(challenges), 2)  # http-01, tls-alpn-01 (no dns-01)\n\n        types = [c[\"type\"] for c in challenges]\n        self.assertIn(\"http-01\", types)\n        self.assertIn(\"tls-alpn-01\", types)\n        self.assertNotIn(\"dns-01\", types)\n\n    def test_036_create_standard_challenge_set_repository_failure(self):\n        \"\"\"Test standard challenge set creation when repository fails\"\"\"\n        self.repository.create_challenge = Mock(return_value=None)\n\n        challenges = self.factory.create_standard_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            value=\"example.com\",\n        )\n\n        self.assertEqual(len(challenges), 0)\n\n    def test_037_create_email_reply_challenge_success(self):\n        \"\"\"Test successful email-reply challenge creation\"\"\"\n        challenge = self.factory.create_email_reply_challenge(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            email_address=\"user@example.com\",\n            sender_address=\"sender@example.com\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"email-reply-00\")\n        self.assertEqual(challenge[\"token\"], \"test-token\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n        self.assertEqual(challenge[\"from\"], \"sender@example.com\")\n        self.assertTrue(challenge[\"url\"].startswith(\"https://example.com/acme/chall/\"))\n\n    def test_038_create_email_reply_challenge_no_sender(self):\n        \"\"\"Test email-reply challenge creation without sender address\"\"\"\n        challenge = self.factory.create_email_reply_challenge(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            email_address=\"user@example.com\",\n            sender_address=\"\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"from\"], \"admin@example.com\")  # Uses factory default\n\n    def test_039_create_email_reply_challenge_repository_failure(self):\n        \"\"\"Test email-reply challenge creation when repository fails\"\"\"\n        self.repository.create_challenge = Mock(return_value=None)\n\n        challenge = self.factory.create_email_reply_challenge(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            email_address=\"user@example.com\",\n            sender_address=\"sender@example.com\",\n        )\n\n        self.assertIsNone(challenge)\n\n    def test_040_create_tkauth_challenge_success(self):\n        \"\"\"Test successful tkauth challenge creation\"\"\"\n        challenge = self.factory.create_tkauth_challenge(\n            authorization_name=\"test-auth\", token=\"test-token\"\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"tkauth-01\")\n        self.assertEqual(challenge[\"token\"], \"test-token\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n        self.assertEqual(challenge[\"tkauth-type\"], \"atc\")\n\n    def test_041_create_tkauth_challenge_repository_failure(self):\n        \"\"\"Test tkauth challenge creation when repository fails\"\"\"\n        self.repository.create_challenge = Mock(return_value=None)\n\n        challenge = self.factory.create_tkauth_challenge(\n            authorization_name=\"test-auth\", token=\"test-token\"\n        )\n\n        self.assertIsNone(challenge)\n\n    def test_042_create_single_challenge_http(self):\n        \"\"\"Test creating single HTTP challenge\"\"\"\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"http-01\",\n            token=\"test-token\",\n            value=\"example.com\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"http-01\")\n        self.assertEqual(challenge[\"token\"], \"test-token\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n\n    def test_043_create_single_challenge_sectigo_email(self):\n        \"\"\"Test creating sectigo-email challenge\"\"\"\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"sectigo-email-01\",\n            token=\"test-token\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"sectigo-email-01\")\n        self.assertEqual(\n            challenge[\"status\"], \"valid\"\n        )  # Sectigo challenges are pre-validated\n        self.assertNotIn(\"token\", challenge)  # Token is removed for sectigo\n\n    @patch.dict(\"sys.modules\", {\"acme_srv.email_handler\": Mock()})\n    def test_044_create_single_challenge_email(self):\n        \"\"\"Test creating email-reply challenge\"\"\"\n        # Set up the factory with an email address\n        self.factory.email_address = \"foo@example.com\"\n\n        # Mock the repository method directly on the instance\n        self.repository.get_challengeinfo_by_challengename = Mock(\n            return_value={\n                \"name\": \"email-reply-00-test-auth-1\",\n                \"keyauthorization\": \"keyauthorization-value\",\n                \"authorization__value\": \"user@example.com\",\n            }\n        )\n\n        # Create a mock EmailHandler class and instance\n        mock_email_handler_instance = Mock()\n        mock_email_handler_class = Mock()\n        mock_email_handler_class.return_value.__enter__ = Mock(\n            return_value=mock_email_handler_instance\n        )\n        mock_email_handler_class.return_value.__exit__ = Mock(return_value=None)\n\n        # Mock the email_handler module\n        import sys\n\n        sys.modules[\"acme_srv.email_handler\"].EmailHandler = mock_email_handler_class\n\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"email-reply-00\",\n            token=\"test-token\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"email-reply-00\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n        self.assertEqual(challenge[\"from\"], \"foo@example.com\")\n\n        # Verify the repository method was called with the right parameters\n        self.repository.get_challengeinfo_by_challengename.assert_called_once_with(\n            \"email-reply-00-test-auth-1\",\n            vlist=(\"name\", \"keyauthorization\", \"authorization__value\"),\n        )\n\n        # Verify EmailHandler was created with logger\n        mock_email_handler_class.assert_called_once_with(logger=self.factory.logger)\n\n        # Verify send_email_challenge was called with correct parameters\n        mock_email_handler_instance.send_email_challenge.assert_called_once_with(\n            to_address=\"user@example.com\", token1=\"keyauthorization-value\"\n        )\n\n    def test_045_create_single_challenge_repository_failure(self):\n        \"\"\"Test single challenge creation when repository fails\"\"\"\n        self.repository.create_challenge = Mock(return_value=None)\n\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\", challenge_type=\"http-01\", token=\"test-token\"\n        )\n\n        self.assertIsNone(challenge)\n\n    def test_046_factory_without_email_address(self):\n        \"\"\"Test factory initialization without email address\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeFactory\n\n        factory = ChallengeFactory(\n            repository=self.repository,\n            logger=self.logger,\n            server_name=\"https://example.com\",\n            challenge_path=\"/acme/chall/\",\n        )\n\n        self.assertIsNone(factory.email_address)\n\n    def test_047_logger_debug_calls_in_factory(self):\n        \"\"\"Test logger debug calls in factory methods\"\"\"\n        self.factory.create_standard_challenge_set(\n            \"test-auth\", \"test-token\", \"dns\", \"example.com\"\n        )\n\n        self.assertTrue(self.logger.debug.called)\n        self.assertGreater(self.logger.debug.call_count, 0)\n\n    def test_048_email_challenge_creation_basic(self):\n        \"\"\"Test basic email challenge creation without triggering email handler\"\"\"\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"http-01\",  # Use http instead of email to avoid import\n            token=\"test-token\",\n            value=\"example.com\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"http-01\")\n        self.assertEqual(challenge[\"token\"], \"test-token\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n\n    def test_049_create_single_challenge_invalid_type(self):\n        \"\"\"Test creating challenge with unknown type\"\"\"\n        challenge = self.factory.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"unknown-01\",\n            token=\"test-token\",\n            value=\"test-value\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge[\"type\"], \"unknown-01\")\n        self.assertEqual(challenge[\"status\"], \"pending\")\n\n    def test_050_create_standard_challenge_set_empty_types(self):\n        \"\"\"Test challenge set creation when no types remain\"\"\"\n        # Mock the factory to have no challenge types (edge case)\n        original_method = self.factory.create_single_challenge\n        self.factory.create_single_challenge = Mock(return_value=None)\n\n        challenges = self.factory.create_standard_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            value=\"example.com\",\n        )\n\n        self.assertEqual(len(challenges), 0)\n        self.factory.create_single_challenge = original_method\n\n    def test_051_factory_email_challenge_without_email_address(self):\n        \"\"\"Test email challenge creation when factory has no email address\"\"\"\n        from acme_srv.challenge_business_logic import ChallengeFactory\n\n        factory_no_email = ChallengeFactory(\n            repository=self.repository,\n            logger=self.logger,\n            server_name=\"https://example.com\",\n            challenge_path=\"/acme/chall/\",\n        )\n\n        challenge = factory_no_email.create_single_challenge(\n            authorization_name=\"test-auth\",\n            challenge_type=\"email-reply-00\",\n            token=\"test-token\",\n            value=\"test@example.com\",\n        )\n\n        self.assertIsNotNone(challenge)\n        self.assertNotIn(\"from\", challenge)\n\n\nclass MockConfig:\n    \"\"\"Mock configuration object for testing\"\"\"\n\n    def __init__(self, **kwargs):\n        self.email_identifier_support = kwargs.get(\"email_identifier_support\", False)\n        self.email_address = kwargs.get(\"email_address\", None)\n        self.tnauthlist_support = kwargs.get(\"tnauthlist_support\", False)\n        self.sectigo_sim = kwargs.get(\"sectigo_sim\", False)\n\n\nclass TestChallengeService(unittest.TestCase):\n    \"\"\"Test cases for ChallengeService\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        from acme_srv.challenge_business_logic import (\n            ChallengeService,\n            ChallengeStateManager,\n            ChallengeFactory,\n            ChallengeInfo,\n        )\n\n        self.logger = Mock(spec=logging.Logger)\n        self.repository = MockChallengeRepository()\n        self.state_manager = Mock(spec=ChallengeStateManager)\n        self.factory = Mock(spec=ChallengeFactory)\n\n        self.service = ChallengeService(\n            repository=self.repository,\n            state_manager=self.state_manager,\n            factory=self.factory,\n            logger=self.logger,\n        )\n\n        self.ChallengeInfo = ChallengeInfo\n\n    def test_052_service_initialization(self):\n        \"\"\"Test ChallengeService initialization\"\"\"\n        self.assertEqual(self.service.repository, self.repository)\n        self.assertEqual(self.service.state_manager, self.state_manager)\n        self.assertEqual(self.service.factory, self.factory)\n        self.assertEqual(self.service.logger, self.logger)\n\n    def test_053_get_challenge_set_with_existing_challenges(self):\n        \"\"\"Test getting challenge set when challenges already exist\"\"\"\n        existing_challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-1\",\n                type=\"http-01\",\n                token=\"token-1\",\n                status=\"pending\",\n                authorization_name=\"test-auth\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n            )\n        ]\n\n        self.repository.find_challenges_by_authorization = Mock(\n            return_value=existing_challenges\n        )\n\n        config = MockConfig()\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"new-token\",\n            id_type=\"dns\",\n            id_value=\"example.com\",\n            config=config,\n            url=\"https://example.com/acme/chall/\",\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"http-01\")\n        self.assertEqual(result[0][\"url\"], \"https://example.com/acme/chall/challenge-1\")\n\n    def test_054_get_challenge_set_create_new_standard(self):\n        \"\"\"Test creating new standard challenge set\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_standard_challenge_set = Mock(\n            return_value=[\n                {\"type\": \"http-01\", \"token\": \"test-token\", \"status\": \"pending\"},\n                {\"type\": \"dns-01\", \"token\": \"test-token\", \"status\": \"pending\"},\n            ]\n        )\n\n        config = MockConfig()\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            id_value=\"example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 2)\n        self.factory.create_standard_challenge_set.assert_called_once_with(\n            \"test-auth\", \"test-token\", \"dns\", \"example.com\"\n        )\n\n    def test_055_get_challenge_set_email_identifier(self):\n        \"\"\"Test creating challenge set for email identifier\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_email_reply_challenge = Mock(\n            return_value={\n                \"type\": \"email-reply-00\",\n                \"token\": \"test-token\",\n                \"status\": \"pending\",\n            }\n        )\n\n        config = MockConfig(\n            email_identifier_support=True, email_address=\"admin@example.com\"\n        )\n\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"user@example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"email-reply-00\")\n        self.factory.create_email_reply_challenge.assert_called_once_with(\n            \"test-auth\", \"test-token\", \"user@example.com\", \"admin@example.com\"\n        )\n\n    def test_056_get_challenge_set_email_identifier_no_config(self):\n        \"\"\"Test email identifier without proper configuration\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_standard_challenge_set = Mock(return_value=[])\n\n        config = MockConfig(email_identifier_support=False)\n\n        self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"user@example.com\",\n            config=config,\n        )\n\n        # Should fall through to standard challenge creation\n        self.factory.create_standard_challenge_set.assert_called_once()\n\n    def test_057_get_challenge_set_tnauthlist_identifier(self):\n        \"\"\"Test creating challenge set for tnauthlist identifier\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_tkauth_challenge = Mock(\n            return_value={\n                \"type\": \"tkauth-01\",\n                \"token\": \"test-token\",\n                \"status\": \"pending\",\n            }\n        )\n\n        config = MockConfig(tnauthlist_support=True)\n\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"tnauthlist\",\n            id_value=\"test-value\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"tkauth-01\")\n        self.factory.create_tkauth_challenge.assert_called_once_with(\n            \"test-auth\", \"test-token\"\n        )\n\n    def test_058_get_challenge_set_sectigo_simulation(self):\n        \"\"\"Test creating challenge set with Sectigo simulation\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_single_challenge = Mock(\n            return_value={\"type\": \"sectigo-email-01\", \"status\": \"valid\"}\n        )\n        self.factory.create_standard_challenge_set = Mock(\n            return_value=[\n                {\"type\": \"http-01\", \"token\": \"test-token\", \"status\": \"pending\"}\n            ]\n        )\n\n        config = MockConfig(sectigo_sim=True)\n\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            id_value=\"example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 2)  # Sectigo + standard challenges\n        types = [c[\"type\"] for c in result]\n        self.assertIn(\"sectigo-email-01\", types)\n        self.assertIn(\"http-01\", types)\n\n    def test_059_format_existing_challenges_basic(self):\n        \"\"\"Test formatting existing challenges\"\"\"\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-1\",\n                type=\"http-01\",\n                token=\"token-1\",\n                status=\"pending\",\n                authorization_name=\"test-auth\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n            ),\n            self.ChallengeInfo(\n                name=\"challenge-2\",\n                type=\"dns-01\",\n                token=\"token-2\",\n                status=\"valid\",\n                authorization_name=\"test-auth\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n            ),\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges,\n            url=\"https://example.com/acme/chall/\",\n            config=MockConfig(),\n        )\n\n        self.assertEqual(len(result), 2)\n        self.assertEqual(result[0][\"type\"], \"http-01\")\n        self.assertEqual(result[0][\"url\"], \"https://example.com/acme/chall/challenge-1\")\n        self.assertEqual(result[1][\"type\"], \"dns-01\")\n        self.assertEqual(result[1][\"status\"], \"valid\")\n\n    def test_060_format_existing_challenges_email_reply(self):\n        \"\"\"Test formatting existing email-reply challenges\"\"\"\n        challenges = [\n            self.ChallengeInfo(\n                name=\"email-challenge-1\",\n                type=\"email-reply-00\",\n                token=\"email-token\",\n                status=\"pending\",\n                authorization_name=\"test-auth\",\n                authorization_type=\"email\",\n                authorization_value=\"user@example.com\",\n                url=\"\",\n            )\n        ]\n\n        config = MockConfig(email_address=\"admin@example.com\")\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"https://example.com/acme/chall/\", config=config\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"email-reply-00\")\n        self.assertEqual(result[0][\"from\"], \"admin@example.com\")\n\n    def test_061_create_new_challenge_set_empty_config(self):\n        \"\"\"Test creating new challenge set with minimal configuration\"\"\"\n        self.factory.create_standard_challenge_set = Mock(\n            return_value=[\n                {\"type\": \"http-01\", \"token\": \"test-token\", \"status\": \"pending\"}\n            ]\n        )\n\n        config = MockConfig()\n\n        result = self.service._create_new_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            id_value=\"example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 1)\n        self.factory.create_standard_challenge_set.assert_called_once()\n\n    def test_062_get_challenge_set_email_challenge_failure(self):\n        \"\"\"Test email challenge creation failure\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_email_reply_challenge = Mock(return_value=None)\n\n        config = MockConfig(\n            email_identifier_support=True, email_address=\"admin@example.com\"\n        )\n\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"user@example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 0)\n\n    def test_063_get_challenge_set_tkauth_challenge_failure(self):\n        \"\"\"Test tkauth challenge creation failure\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_tkauth_challenge = Mock(return_value=None)\n\n        config = MockConfig(tnauthlist_support=True)\n\n        result = self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"TNAUTHLIST\",  # Test case insensitive\n            id_value=\"test-value\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 0)\n\n    def test_064_logger_debug_calls_in_service(self):\n        \"\"\"Test logger debug calls in service methods\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_standard_challenge_set = Mock(return_value=[])\n\n        config = MockConfig()\n        self.service.get_challenge_set_for_authorization(\n            \"test-auth\", \"test-token\", \"dns\", \"example.com\", config\n        )\n\n        self.assertTrue(self.logger.debug.called)\n\n    def test_065_sectigo_challenge_creation_failure(self):\n        \"\"\"Test sectigo challenge creation failure\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_single_challenge = Mock(return_value=None)\n        self.factory.create_standard_challenge_set = Mock(return_value=[])\n\n        config = MockConfig(sectigo_sim=True)\n\n        self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"dns\",\n            id_value=\"example.com\",\n            config=config,\n        )\n\n        # Should still return standard challenges even if sectigo fails\n        self.factory.create_standard_challenge_set.assert_called_once()\n\n    def test_066_email_identifier_edge_cases(self):\n        \"\"\"Test email identifier with edge cases\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_standard_challenge_set = Mock(return_value=[])\n\n        # Test with email_identifier_support=True but no email_address\n        config = MockConfig(email_identifier_support=True, email_address=None)\n        self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"user@example.com\",\n            config=config,\n        )\n\n        # Should fall through to standard challenges\n        self.factory.create_standard_challenge_set.assert_called_once()\n\n        # Test with email_address but no @ in id_value\n        config = MockConfig(\n            email_identifier_support=True, email_address=\"admin@example.com\"\n        )\n        self.service.get_challenge_set_for_authorization(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"notanemail\",\n            config=config,\n        )\n\n        # Should still try to create email challenge\n        self.assertEqual(self.factory.create_standard_challenge_set.call_count, 2)\n\n    def test_067_service_repository_exception_handling(self):\n        \"\"\"Test service behavior when repository raises exceptions\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(\n            side_effect=Exception(\"Database error\")\n        )\n        config = MockConfig()\n\n        # The implementation doesn't handle exceptions, so this will raise\n        with self.assertRaises(Exception):\n            self.service.get_challenge_set_for_authorization(\n                \"test-auth\", \"test-token\", \"dns\", \"example.com\", config\n            )\n\n    def test_068_format_existing_challenges_empty_list(self):\n        \"\"\"Test formatting empty challenge list\"\"\"\n        result = self.service._format_existing_challenges(\n            challenges=[], url=\"https://example.com/acme/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 0)\n        self.assertIsInstance(result, list)\n\n    def test_069_format_existing_challenges_no_url(self):\n        \"\"\"Test formatting challenges without URL\"\"\"\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-1\",\n                type=\"http-01\",\n                token=\"token-1\",\n                status=\"pending\",\n                authorization_name=\"test-auth\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"url\"], \"challenge-1\")  # Just the challenge name\n\n    def test_069_1_format_existing_challenges_with_valid_json_validation_error(self):\n        \"\"\"Test _format_existing_challenges with valid JSON validation_error (lines 421-430)\"\"\"\n        error_obj = {\n            \"type\": \"urn:ietf:params:acme:error:dns\",\n            \"detail\": \"DNS query failed\",\n            \"status\": 400,\n        }\n        json_error = json.dumps(error_obj)\n\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-1\",\n                type=\"dns-01\",\n                token=\"token-1\",\n                status=\"invalid\",\n                authorization_name=\"auth-1\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=json_error,\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"dns-01\")\n        self.assertEqual(result[0][\"status\"], \"invalid\")\n        self.assertEqual(result[0][\"error\"], error_obj)  # Should be parsed JSON\n\n    def test_069_2_format_existing_challenges_with_invalid_json_validation_error(self):\n        \"\"\"Test _format_existing_challenges with invalid JSON validation_error (lines 421-430)\"\"\"\n        invalid_json_error = \"This is not valid JSON {{\"\n\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-2\",\n                type=\"http-01\",\n                token=\"token-2\",\n                status=\"invalid\",\n                authorization_name=\"auth-2\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=invalid_json_error,\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"http-01\")\n        self.assertEqual(result[0][\"status\"], \"invalid\")\n        # Should create default error structure when JSON parsing fails\n        expected_error = {\n            \"status\": 400,\n            \"type\": \"urn:ietf:params:acme:error:unknown\",\n            \"detail\": invalid_json_error,\n        }\n        self.assertEqual(result[0][\"error\"], expected_error)\n\n    def test_069_3_format_existing_challenges_with_empty_validation_error(self):\n        \"\"\"Test _format_existing_challenges with empty validation_error (lines 421-430)\"\"\"\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-3\",\n                type=\"tls-alpn-01\",\n                token=\"token-3\",\n                status=\"invalid\",\n                authorization_name=\"auth-3\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=\"\",  # Empty string\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"tls-alpn-01\")\n        self.assertEqual(result[0][\"status\"], \"invalid\")\n        # Empty string is falsy, so no error key should be added\n        self.assertNotIn(\"error\", result[0])\n\n    def test_069_3_2_format_existing_challenges_with_whitespace_validation_error(self):\n        \"\"\"Test _format_existing_challenges with whitespace-only validation_error (lines 421-430)\"\"\"\n        whitespace_error = \"   \"  # Only whitespace - still truthy\n\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-3-2\",\n                type=\"tls-alpn-01\",\n                token=\"token-3-2\",\n                status=\"invalid\",\n                authorization_name=\"auth-3-2\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=whitespace_error,\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"tls-alpn-01\")\n        self.assertEqual(result[0][\"status\"], \"invalid\")\n        # Should create default error structure for whitespace validation_error\n        expected_error = {\n            \"status\": 400,\n            \"type\": \"urn:ietf:params:acme:error:unknown\",\n            \"detail\": whitespace_error,\n        }\n        self.assertEqual(result[0][\"error\"], expected_error)\n\n    def test_069_4_format_existing_challenges_with_none_validation_error(self):\n        \"\"\"Test _format_existing_challenges with None validation_error (lines 421-430)\"\"\"\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-4\",\n                type=\"http-01\",\n                token=\"token-4\",\n                status=\"valid\",  # Valid challenges shouldn't have validation errors\n                authorization_name=\"auth-4\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=None,\n            )\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"http-01\")\n        self.assertEqual(result[0][\"status\"], \"valid\")\n        # Should not have error key when validation_error is None\n        self.assertNotIn(\"error\", result[0])\n\n    def test_069_5_format_existing_challenges_multiple_errors(self):\n        \"\"\"Test _format_existing_challenges with multiple challenges having different error types (lines 421-430)\"\"\"\n        valid_error = json.dumps(\n            {\"type\": \"urn:ietf:params:acme:error:dns\", \"detail\": \"Valid JSON error\"}\n        )\n        invalid_error = \"Invalid JSON error\"\n\n        challenges = [\n            self.ChallengeInfo(\n                name=\"challenge-valid-json\",\n                type=\"dns-01\",\n                token=\"token-1\",\n                status=\"invalid\",\n                authorization_name=\"auth-1\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=valid_error,\n            ),\n            self.ChallengeInfo(\n                name=\"challenge-invalid-json\",\n                type=\"http-01\",\n                token=\"token-2\",\n                status=\"invalid\",\n                authorization_name=\"auth-2\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=invalid_error,\n            ),\n            self.ChallengeInfo(\n                name=\"challenge-no-error\",\n                type=\"tls-alpn-01\",\n                token=\"token-3\",\n                status=\"pending\",\n                authorization_name=\"auth-3\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n                url=\"\",\n                validation_error=None,\n            ),\n        ]\n\n        result = self.service._format_existing_challenges(\n            challenges=challenges, url=\"http://example.com/chall/\", config=MockConfig()\n        )\n\n        self.assertEqual(len(result), 3)\n\n        # First challenge - valid JSON error\n        self.assertEqual(\n            result[0][\"error\"],\n            {\"type\": \"urn:ietf:params:acme:error:dns\", \"detail\": \"Valid JSON error\"},\n        )\n\n        # Second challenge - invalid JSON error\n        expected_error = {\n            \"status\": 400,\n            \"type\": \"urn:ietf:params:acme:error:unknown\",\n            \"detail\": invalid_error,\n        }\n        self.assertEqual(result[1][\"error\"], expected_error)\n\n        # Third challenge - no error\n        self.assertNotIn(\"error\", result[2])\n\n    def test_070_create_new_challenge_set_all_types_enabled(self):\n        \"\"\"Test creating challenge set with all special types enabled\"\"\"\n        self.factory.create_email_reply_challenge = Mock(\n            return_value={\"type\": \"email-reply-00\", \"status\": \"pending\"}\n        )\n        self.factory.create_tkauth_challenge = Mock(\n            return_value={\"type\": \"tkauth-01\", \"status\": \"pending\"}\n        )\n        self.factory.create_single_challenge = Mock(\n            return_value={\"type\": \"sectigo-email-01\", \"status\": \"valid\"}\n        )\n        self.factory.create_standard_challenge_set = Mock(\n            return_value=[{\"type\": \"http-01\", \"status\": \"pending\"}]\n        )\n\n        # Test email identifier with tnauthlist and sectigo enabled\n        # (should only create email challenge)\n        config = MockConfig(\n            email_identifier_support=True,\n            email_address=\"admin@example.com\",\n            tnauthlist_support=True,\n            sectigo_sim=True,\n        )\n\n        result = self.service._create_new_challenge_set(\n            authorization_name=\"test-auth\",\n            token=\"test-token\",\n            id_type=\"email\",\n            id_value=\"user@example.com\",\n            config=config,\n        )\n\n        self.assertEqual(len(result), 1)\n        self.assertEqual(result[0][\"type\"], \"email-reply-00\")\n\n        # Standard and other challenges should not be called for email identifier\n        self.factory.create_tkauth_challenge.assert_not_called()\n        self.factory.create_single_challenge.assert_not_called()\n        self.factory.create_standard_challenge_set.assert_not_called()\n\n    def test_071_get_challenge_set_mixed_case_id_types(self):\n        \"\"\"Test challenge set creation with mixed case ID types\"\"\"\n        self.repository.find_challenges_by_authorization = Mock(return_value=[])\n        self.factory.create_tkauth_challenge = Mock(\n            return_value={\"type\": \"tkauth-01\", \"status\": \"pending\"}\n        )\n\n        config = MockConfig(tnauthlist_support=True)\n\n        # Test various case variations\n        for id_type in [\"TNAUTHLIST\", \"TnAuthList\", \"tnauthlist\", \"TnAuThLiSt\"]:\n            result = self.service.get_challenge_set_for_authorization(\n                authorization_name=\"test-auth\",\n                token=\"test-token\",\n                id_type=id_type,\n                id_value=\"test-value\",\n                config=config,\n            )\n\n            self.assertEqual(len(result), 1)\n            self.assertEqual(result[0][\"type\"], \"tkauth-01\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_challenge_error_handling.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unit tests for challenge_error_handling.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport logging\nfrom unittest.mock import Mock, patch\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n# Import the module under test\nfrom acme_srv.challenge_error_handling import (\n    ErrorCategory,\n    ErrorSeverity,\n    ErrorDetail,\n    ChallengeError,\n    ValidationError,\n    NetworkError,\n    DatabaseError,\n    ConfigurationError,\n    AuthenticationError,\n    MalformedRequestError,\n    TimeoutError,\n    UnsupportedChallengeTypeError,\n    DNSResolutionError,\n    HTTPChallengeError,\n    DNSChallengeError,\n    TLSALPNChallengeError,\n    ErrorHandler,\n    ErrorRecovery,\n)\n\n\nclass TestErrorCategory(unittest.TestCase):\n    \"\"\"Test cases for ErrorCategory enum\"\"\"\n\n    def test_001_error_category_values(self):\n        \"\"\"Test ErrorCategory enum has correct values\"\"\"\n        self.assertEqual(ErrorCategory.VALIDATION_ERROR.value, \"validation_error\")\n        self.assertEqual(ErrorCategory.NETWORK_ERROR.value, \"network_error\")\n        self.assertEqual(ErrorCategory.DATABASE_ERROR.value, \"database_error\")\n        self.assertEqual(ErrorCategory.CONFIGURATION_ERROR.value, \"configuration_error\")\n        self.assertEqual(\n            ErrorCategory.AUTHENTICATION_ERROR.value, \"authentication_error\"\n        )\n        self.assertEqual(ErrorCategory.MALFORMED_REQUEST.value, \"malformed_request\")\n        self.assertEqual(ErrorCategory.TIMEOUT_ERROR.value, \"timeout_error\")\n        self.assertEqual(ErrorCategory.UNKNOWN_ERROR.value, \"unknown_error\")\n\n    def test_002_error_category_completeness(self):\n        \"\"\"Test that all expected error categories exist\"\"\"\n        expected_categories = {\n            \"validation_error\",\n            \"network_error\",\n            \"database_error\",\n            \"configuration_error\",\n            \"authentication_error\",\n            \"malformed_request\",\n            \"timeout_error\",\n            \"unknown_error\",\n        }\n        actual_categories = {category.value for category in ErrorCategory}\n        self.assertEqual(expected_categories, actual_categories)\n\n    def test_003_error_category_enum_behavior(self):\n        \"\"\"Test ErrorCategory enum behavior\"\"\"\n        # Test enum can be compared\n        self.assertEqual(ErrorCategory.VALIDATION_ERROR, ErrorCategory.VALIDATION_ERROR)\n        self.assertNotEqual(ErrorCategory.VALIDATION_ERROR, ErrorCategory.NETWORK_ERROR)\n\n        # Test enum can be used in sets and dicts\n        category_set = {ErrorCategory.VALIDATION_ERROR, ErrorCategory.NETWORK_ERROR}\n        self.assertEqual(len(category_set), 2)\n\n        category_dict = {ErrorCategory.VALIDATION_ERROR: \"test\"}\n        self.assertEqual(category_dict[ErrorCategory.VALIDATION_ERROR], \"test\")\n\n\nclass TestErrorSeverity(unittest.TestCase):\n    \"\"\"Test cases for ErrorSeverity enum\"\"\"\n\n    def test_001_error_severity_values(self):\n        \"\"\"Test ErrorSeverity enum has correct values\"\"\"\n        self.assertEqual(ErrorSeverity.LOW.value, \"low\")\n        self.assertEqual(ErrorSeverity.MEDIUM.value, \"medium\")\n        self.assertEqual(ErrorSeverity.HIGH.value, \"high\")\n        self.assertEqual(ErrorSeverity.CRITICAL.value, \"critical\")\n\n    def test_002_error_severity_completeness(self):\n        \"\"\"Test that all expected severity levels exist\"\"\"\n        expected_severities = {\"low\", \"medium\", \"high\", \"critical\"}\n        actual_severities = {severity.value for severity in ErrorSeverity}\n        self.assertEqual(expected_severities, actual_severities)\n\n    def test_003_error_severity_enum_behavior(self):\n        \"\"\"Test ErrorSeverity enum behavior\"\"\"\n        # Test enum can be compared\n        self.assertEqual(ErrorSeverity.HIGH, ErrorSeverity.HIGH)\n        self.assertNotEqual(ErrorSeverity.HIGH, ErrorSeverity.LOW)\n\n\nclass TestErrorDetail(unittest.TestCase):\n    \"\"\"Test cases for ErrorDetail dataclass\"\"\"\n\n    def test_001_error_detail_creation_minimal(self):\n        \"\"\"Test ErrorDetail creation with minimal parameters\"\"\"\n        detail = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Test error message\",\n        )\n\n        self.assertEqual(detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(detail.severity, ErrorSeverity.MEDIUM)\n        self.assertEqual(detail.message, \"Test error message\")\n        self.assertIsNone(detail.details)\n        self.assertIsNone(detail.suggestion)\n        self.assertIsNone(detail.error_code)\n\n    def test_002_error_detail_creation_full(self):\n        \"\"\"Test ErrorDetail creation with all parameters\"\"\"\n        test_details = {\"key\": \"value\", \"count\": 42}\n        detail = ErrorDetail(\n            category=ErrorCategory.NETWORK_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Network timeout\",\n            details=test_details,\n            suggestion=\"Check network connectivity\",\n            error_code=\"NET_001\",\n        )\n\n        self.assertEqual(detail.category, ErrorCategory.NETWORK_ERROR)\n        self.assertEqual(detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(detail.message, \"Network timeout\")\n        self.assertEqual(detail.details, test_details)\n        self.assertEqual(detail.suggestion, \"Check network connectivity\")\n        self.assertEqual(detail.error_code, \"NET_001\")\n\n    def test_003_error_detail_dataclass_behavior(self):\n        \"\"\"Test ErrorDetail dataclass behavior\"\"\"\n        detail1 = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Test\",\n        )\n        detail2 = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Test\",\n        )\n        detail3 = ErrorDetail(\n            category=ErrorCategory.NETWORK_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Test\",\n        )\n\n        # Test equality\n        self.assertEqual(detail1, detail2)\n        self.assertNotEqual(detail1, detail3)\n\n        # Test string representation\n        self.assertIn(\"ErrorDetail\", str(detail1))\n        self.assertIn(\"validation_error\", str(detail1))\n\n\nclass TestChallengeError(unittest.TestCase):\n    \"\"\"Test cases for ChallengeError base exception\"\"\"\n\n    def test_001_challenge_error_minimal_creation(self):\n        \"\"\"Test ChallengeError creation with minimal parameters\"\"\"\n        error = ChallengeError(\"Test error message\")\n\n        self.assertEqual(str(error), \"Test error message\")\n        self.assertIsInstance(error.error_detail, ErrorDetail)\n        self.assertEqual(error.error_detail.message, \"Test error message\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.UNKNOWN_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.MEDIUM)\n        self.assertEqual(error.error_detail.details, {})\n        self.assertIsNone(error.error_detail.suggestion)\n        self.assertIsNone(error.error_detail.error_code)\n\n    def test_002_challenge_error_full_creation(self):\n        \"\"\"Test ChallengeError creation with all parameters\"\"\"\n        test_details = {\"test\": \"data\"}\n        error = ChallengeError(\n            message=\"Detailed error\",\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            details=test_details,\n            suggestion=\"Fix the validation\",\n            error_code=\"VAL_001\",\n        )\n\n        self.assertEqual(str(error), \"Detailed error\")\n        self.assertEqual(error.error_detail.message, \"Detailed error\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, test_details)\n        self.assertEqual(error.error_detail.suggestion, \"Fix the validation\")\n        self.assertEqual(error.error_detail.error_code, \"VAL_001\")\n\n    def test_003_challenge_error_inheritance(self):\n        \"\"\"Test ChallengeError inheritance from Exception\"\"\"\n        error = ChallengeError(\"Test\")\n        self.assertIsInstance(error, Exception)\n\n        # Test it can be raised and caught\n        with self.assertRaises(ChallengeError) as context:\n            raise error\n        self.assertEqual(str(context.exception), \"Test\")\n\n    def test_004_challenge_error_none_details(self):\n        \"\"\"Test ChallengeError handles None details correctly\"\"\"\n        error = ChallengeError(\"Test\", details=None)\n        self.assertEqual(error.error_detail.details, {})\n\n\nclass TestValidationError(unittest.TestCase):\n    \"\"\"Test cases for ValidationError exception\"\"\"\n\n    def test_001_validation_error_creation(self):\n        \"\"\"Test ValidationError creation and inheritance\"\"\"\n        error = ValidationError(\"Validation failed\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Validation failed\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.MEDIUM)\n\n    def test_002_validation_error_with_kwargs(self):\n        \"\"\"Test ValidationError with additional parameters\"\"\"\n        error = ValidationError(\n            \"Validation failed\",\n            severity=ErrorSeverity.HIGH,\n            details={\"field\": \"test\"},\n            suggestion=\"Check field format\",\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, {\"field\": \"test\"})\n        self.assertEqual(error.error_detail.suggestion, \"Check field format\")\n\n\nclass TestNetworkError(unittest.TestCase):\n    \"\"\"Test cases for NetworkError exception\"\"\"\n\n    def test_001_network_error_creation(self):\n        \"\"\"Test NetworkError creation and inheritance\"\"\"\n        error = NetworkError(\"Connection failed\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Connection failed\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.NETWORK_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.MEDIUM)\n\n    def test_002_network_error_with_kwargs(self):\n        \"\"\"Test NetworkError with additional parameters\"\"\"\n        error = NetworkError(\n            \"Connection timeout\", severity=ErrorSeverity.HIGH, details={\"timeout\": 30}\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.NETWORK_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, {\"timeout\": 30})\n\n\nclass TestDatabaseError(unittest.TestCase):\n    \"\"\"Test cases for DatabaseError exception\"\"\"\n\n    def test_001_database_error_creation(self):\n        \"\"\"Test DatabaseError creation and default HIGH severity\"\"\"\n        error = DatabaseError(\"Database connection lost\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Database connection lost\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.DATABASE_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n\n    def test_002_database_error_with_kwargs(self):\n        \"\"\"Test DatabaseError with additional parameters\"\"\"\n        error = DatabaseError(\n            \"Query failed\",\n            details={\"query\": \"SELECT * FROM users\"},\n            suggestion=\"Check database schema\",\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.DATABASE_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, {\"query\": \"SELECT * FROM users\"})\n\n\nclass TestConfigurationError(unittest.TestCase):\n    \"\"\"Test cases for ConfigurationError exception\"\"\"\n\n    def test_001_configuration_error_creation(self):\n        \"\"\"Test ConfigurationError creation and default HIGH severity\"\"\"\n        error = ConfigurationError(\"Invalid configuration\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Invalid configuration\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.CONFIGURATION_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n\n    def test_002_configuration_error_with_kwargs(self):\n        \"\"\"Test ConfigurationError with additional parameters\"\"\"\n        error = ConfigurationError(\n            \"Missing required setting\",\n            details={\"setting\": \"api_key\"},\n            error_code=\"CONF_001\",\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.CONFIGURATION_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, {\"setting\": \"api_key\"})\n        self.assertEqual(error.error_detail.error_code, \"CONF_001\")\n\n\nclass TestAuthenticationError(unittest.TestCase):\n    \"\"\"Test cases for AuthenticationError exception\"\"\"\n\n    def test_001_authentication_error_creation(self):\n        \"\"\"Test AuthenticationError creation and default HIGH severity\"\"\"\n        error = AuthenticationError(\"Invalid credentials\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Invalid credentials\")\n        self.assertEqual(\n            error.error_detail.category, ErrorCategory.AUTHENTICATION_ERROR\n        )\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n\n    def test_002_authentication_error_with_kwargs(self):\n        \"\"\"Test AuthenticationError with additional parameters\"\"\"\n        error = AuthenticationError(\n            \"Token expired\",\n            details={\"token_type\": \"JWT\"},\n            suggestion=\"Refresh the authentication token\",\n        )\n\n        self.assertEqual(\n            error.error_detail.category, ErrorCategory.AUTHENTICATION_ERROR\n        )\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.HIGH)\n        self.assertEqual(error.error_detail.details, {\"token_type\": \"JWT\"})\n\n\nclass TestMalformedRequestError(unittest.TestCase):\n    \"\"\"Test cases for MalformedRequestError exception\"\"\"\n\n    def test_001_malformed_request_error_creation(self):\n        \"\"\"Test MalformedRequestError creation\"\"\"\n        error = MalformedRequestError(\"Invalid JSON format\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Invalid JSON format\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.MALFORMED_REQUEST)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.MEDIUM)\n\n    def test_002_malformed_request_error_with_kwargs(self):\n        \"\"\"Test MalformedRequestError with additional parameters\"\"\"\n        error = MalformedRequestError(\n            \"Missing required field\",\n            details={\"field\": \"challenge_type\"},\n            error_code=\"MALFORMED_001\",\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.MALFORMED_REQUEST)\n        self.assertEqual(error.error_detail.details, {\"field\": \"challenge_type\"})\n        self.assertEqual(error.error_detail.error_code, \"MALFORMED_001\")\n\n\nclass TestTimeoutError(unittest.TestCase):\n    \"\"\"Test cases for TimeoutError exception\"\"\"\n\n    def test_001_timeout_error_creation(self):\n        \"\"\"Test TimeoutError creation\"\"\"\n        error = TimeoutError(\"Operation timed out\")\n\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Operation timed out\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.TIMEOUT_ERROR)\n        self.assertEqual(error.error_detail.severity, ErrorSeverity.MEDIUM)\n\n    def test_002_timeout_error_with_kwargs(self):\n        \"\"\"Test TimeoutError with additional parameters\"\"\"\n        error = TimeoutError(\n            \"HTTP request timeout\",\n            details={\"timeout\": 30, \"url\": \"https://example.com\"},\n            suggestion=\"Increase timeout or check network connection\",\n        )\n\n        self.assertEqual(error.error_detail.category, ErrorCategory.TIMEOUT_ERROR)\n        self.assertEqual(\n            error.error_detail.details, {\"timeout\": 30, \"url\": \"https://example.com\"}\n        )\n\n\nclass TestUnsupportedChallengeTypeError(unittest.TestCase):\n    \"\"\"Test cases for UnsupportedChallengeTypeError exception\"\"\"\n\n    def test_001_unsupported_challenge_type_error_creation(self):\n        \"\"\"Test UnsupportedChallengeTypeError creation\"\"\"\n        supported_types = [\"http-01\", \"dns-01\", \"tls-alpn-01\"]\n        error = UnsupportedChallengeTypeError(\"custom-challenge\", supported_types)\n\n        self.assertIsInstance(error, ValidationError)\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"Unsupported challenge type: custom-challenge\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.error_code, \"UNSUPPORTED_CHALLENGE_TYPE\")\n\n        expected_details = {\n            \"challenge_type\": \"custom-challenge\",\n            \"supported_types\": supported_types,\n        }\n        self.assertEqual(error.error_detail.details, expected_details)\n        self.assertEqual(\n            error.error_detail.suggestion,\n            \"Use one of the supported types: http-01, dns-01, tls-alpn-01\",\n        )\n\n    def test_002_unsupported_challenge_type_error_empty_supported_types(self):\n        \"\"\"Test UnsupportedChallengeTypeError with empty supported types\"\"\"\n        error = UnsupportedChallengeTypeError(\"unknown\", [])\n\n        self.assertEqual(str(error), \"Unsupported challenge type: unknown\")\n        self.assertEqual(error.error_detail.details[\"supported_types\"], [])\n        self.assertEqual(\n            error.error_detail.suggestion, \"Use one of the supported types: \"\n        )\n\n\nclass TestDNSResolutionError(unittest.TestCase):\n    \"\"\"Test cases for DNSResolutionError exception\"\"\"\n\n    def test_001_dns_resolution_error_basic(self):\n        \"\"\"Test DNSResolutionError creation without DNS servers\"\"\"\n        error = DNSResolutionError(\"example.com\")\n\n        self.assertIsInstance(error, NetworkError)\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), \"DNS resolution failed for domain: example.com\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.NETWORK_ERROR)\n        self.assertEqual(error.error_detail.error_code, \"DNS_RESOLUTION_FAILED\")\n\n        expected_details = {\"domain\": \"example.com\", \"dns_servers\": None}\n        self.assertEqual(error.error_detail.details, expected_details)\n        self.assertEqual(\n            error.error_detail.suggestion,\n            \"Check domain validity and DNS server configuration\",\n        )\n\n    def test_002_dns_resolution_error_with_dns_servers(self):\n        \"\"\"Test DNSResolutionError creation with DNS servers\"\"\"\n        dns_servers = [\"8.8.8.8\", \"1.1.1.1\"]\n        error = DNSResolutionError(\"test.example.com\", dns_servers)\n\n        self.assertEqual(\n            str(error), \"DNS resolution failed for domain: test.example.com\"\n        )\n        expected_details = {\"domain\": \"test.example.com\", \"dns_servers\": dns_servers}\n        self.assertEqual(error.error_detail.details, expected_details)\n\n\nclass TestHTTPChallengeError(unittest.TestCase):\n    \"\"\"Test cases for HTTPChallengeError exception\"\"\"\n\n    def test_001_http_challenge_error_creation(self):\n        \"\"\"Test HTTPChallengeError creation\"\"\"\n        url = \"http://example.com/.well-known/acme-challenge/token\"\n        expected = \"expected_token_response\"\n        received = \"unexpected_response\"\n\n        error = HTTPChallengeError(url, expected, received)\n\n        self.assertIsInstance(error, ValidationError)\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(str(error), f\"HTTP challenge validation failed for {url}\")\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.error_code, \"HTTP_CHALLENGE_FAILED\")\n\n        expected_details = {\n            \"url\": url,\n            \"expected_response\": expected,\n            \"received_response\": received,\n        }\n        self.assertEqual(error.error_detail.details, expected_details)\n        self.assertEqual(\n            error.error_detail.suggestion,\n            \"Ensure the challenge file is accessible and contains the correct token\",\n        )\n\n    def test_002_http_challenge_error_empty_responses(self):\n        \"\"\"Test HTTPChallengeError with empty responses\"\"\"\n        error = HTTPChallengeError(\"http://test.com/acme\", \"\", \"\")\n\n        self.assertEqual(error.error_detail.details[\"expected_response\"], \"\")\n        self.assertEqual(error.error_detail.details[\"received_response\"], \"\")\n\n\nclass TestDNSChallengeError(unittest.TestCase):\n    \"\"\"Test cases for DNSChallengeError exception\"\"\"\n\n    def test_001_dns_challenge_error_creation(self):\n        \"\"\"Test DNSChallengeError creation\"\"\"\n        dns_record = \"_acme-challenge.example.com\"\n        expected_hash = \"expected_hash_value\"\n        found_records = [\"wrong_hash1\", \"wrong_hash2\"]\n\n        error = DNSChallengeError(dns_record, expected_hash, found_records)\n\n        self.assertIsInstance(error, ValidationError)\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(\n            str(error), f\"DNS challenge validation failed for {dns_record}\"\n        )\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.error_code, \"DNS_CHALLENGE_FAILED\")\n\n        expected_details = {\n            \"dns_record\": dns_record,\n            \"expected_hash\": expected_hash,\n            \"found_records\": found_records,\n        }\n        self.assertEqual(error.error_detail.details, expected_details)\n        self.assertEqual(\n            error.error_detail.suggestion,\n            \"Ensure the DNS TXT record is properly configured\",\n        )\n\n    def test_002_dns_challenge_error_empty_found_records(self):\n        \"\"\"Test DNSChallengeError with empty found records\"\"\"\n        error = DNSChallengeError(\"_acme-challenge.test.com\", \"hash123\", [])\n\n        self.assertEqual(error.error_detail.details[\"found_records\"], [])\n\n\nclass TestTLSALPNChallengeError(unittest.TestCase):\n    \"\"\"Test cases for TLSALPNChallengeError exception\"\"\"\n\n    def test_001_tls_alpn_challenge_error_creation(self):\n        \"\"\"Test TLSALPNChallengeError creation\"\"\"\n        domain = \"example.com\"\n        expected_extension = \"acme-tls/1\"\n\n        error = TLSALPNChallengeError(domain, expected_extension)\n\n        self.assertIsInstance(error, ValidationError)\n        self.assertIsInstance(error, ChallengeError)\n        self.assertEqual(\n            str(error), f\"TLS-ALPN challenge validation failed for {domain}\"\n        )\n        self.assertEqual(error.error_detail.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertEqual(error.error_detail.error_code, \"TLS_ALPN_CHALLENGE_FAILED\")\n\n        expected_details = {\"domain\": domain, \"expected_extension\": expected_extension}\n        self.assertEqual(error.error_detail.details, expected_details)\n        self.assertEqual(\n            error.error_detail.suggestion,\n            \"Ensure the TLS certificate contains the required extension\",\n        )\n\n\nclass TestErrorHandler(unittest.TestCase):\n    \"\"\"Test cases for ErrorHandler class\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for ErrorHandler tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.logger.isEnabledFor.return_value = False\n        self.error_handler = ErrorHandler(self.logger)\n\n    def test_001_error_handler_initialization(self):\n        \"\"\"Test ErrorHandler initialization\"\"\"\n        self.assertEqual(self.error_handler.logger, self.logger)\n        self.assertEqual(self.error_handler.error_counts, {})\n\n    def test_002_handle_challenge_error(self):\n        \"\"\"Test handling ChallengeError instances\"\"\"\n        error = ValidationError(\"Test validation error\")\n        context = {\"test_context\": \"value\"}\n\n        result = self.error_handler.handle_error(error, context)\n\n        self.assertIsInstance(result, ErrorDetail)\n        self.assertEqual(result.message, \"Test validation error\")\n        self.assertEqual(result.category, ErrorCategory.VALIDATION_ERROR)\n        self.assertIn(\"test_context\", result.details)\n        self.assertEqual(result.details[\"test_context\"], \"value\")\n\n    def test_003_handle_generic_exception(self):\n        \"\"\"Test handling generic Exception instances\"\"\"\n        error = ValueError(\"Generic error message\")\n        context = {\"operation\": \"test_operation\"}\n\n        result = self.error_handler.handle_error(error, context)\n\n        self.assertIsInstance(result, ErrorDetail)\n        self.assertEqual(result.message, \"Generic error message\")\n        self.assertEqual(result.category, ErrorCategory.UNKNOWN_ERROR)\n        self.assertEqual(result.severity, ErrorSeverity.MEDIUM)\n        self.assertEqual(result.details[\"exception_type\"], \"ValueError\")\n        self.assertEqual(result.details[\"operation\"], \"test_operation\")\n\n    def test_004_handle_error_without_context(self):\n        \"\"\"Test handling error without context\"\"\"\n        error = NetworkError(\"Network failure\")\n\n        result = self.error_handler.handle_error(error)\n\n        self.assertIsInstance(result, ErrorDetail)\n        self.assertEqual(result.message, \"Network failure\")\n        self.assertEqual(result.category, ErrorCategory.NETWORK_ERROR)\n\n    def test_005_log_error_critical_severity(self):\n        \"\"\"Test logging error with CRITICAL severity\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.DATABASE_ERROR,\n            severity=ErrorSeverity.CRITICAL,\n            message=\"Critical database error\",\n            details={\"connection\": \"lost\"},\n        )\n        original_error = Exception(\"Test error\")\n\n        self.error_handler._log_error(error_detail, original_error)\n\n        self.logger.critical.assert_called_once()\n        log_message = self.logger.critical.call_args[0][0]\n        self.assertIn(\"[database_error]\", log_message)\n        self.assertIn(\"Critical database error\", log_message)\n        self.assertIn(\"Details:\", log_message)\n\n    def test_006_log_error_high_severity(self):\n        \"\"\"Test logging error with HIGH severity\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.CONFIGURATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"High severity error\",\n        )\n        original_error = Exception(\"Test error\")\n\n        self.error_handler._log_error(error_detail, original_error)\n\n        self.logger.error.assert_called_once()\n        log_message = self.logger.error.call_args[0][0]\n        self.assertIn(\"[configuration_error]\", log_message)\n        self.assertIn(\"High severity error\", log_message)\n\n    def test_007_log_error_medium_severity(self):\n        \"\"\"Test logging error with MEDIUM severity\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Medium severity error\",\n        )\n        original_error = Exception(\"Test error\")\n\n        self.error_handler._log_error(error_detail, original_error)\n\n        self.logger.warning.assert_called_once()\n        log_message = self.logger.warning.call_args[0][0]\n        self.assertIn(\"[validation_error]\", log_message)\n        self.assertIn(\"Medium severity error\", log_message)\n\n    def test_008_log_error_low_severity(self):\n        \"\"\"Test logging error with LOW severity\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.UNKNOWN_ERROR,\n            severity=ErrorSeverity.LOW,\n            message=\"Low severity error\",\n        )\n        original_error = Exception(\"Test error\")\n\n        self.error_handler._log_error(error_detail, original_error)\n\n        self.logger.info.assert_called_once()\n        log_message = self.logger.info.call_args[0][0]\n        self.assertIn(\"[unknown_error]\", log_message)\n        self.assertIn(\"Low severity error\", log_message)\n\n    def test_009_log_error_debug_mode(self):\n        \"\"\"Test logging error in debug mode with stack trace\"\"\"\n        self.logger.isEnabledFor.return_value = True\n\n        error_detail = ErrorDetail(\n            category=ErrorCategory.NETWORK_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Debug mode error\",\n        )\n        original_error = Exception(\"Debug error\")\n\n        with patch(\"traceback.format_exception\") as mock_format:\n            mock_format.return_value = [\"Traceback line 1\\n\", \"Traceback line 2\\n\"]\n\n            self.error_handler._log_error(error_detail, original_error)\n\n            self.logger.warning.assert_called_once()\n            self.logger.debug.assert_called_with(\n                \"Stack trace for error: %s\", \"Traceback line 1\\nTraceback line 2\\n\"\n            )\n\n    def test_010_log_error_without_details(self):\n        \"\"\"Test logging error without details\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.TIMEOUT_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Simple timeout error\",\n        )\n        original_error = Exception(\"Test error\")\n\n        self.error_handler._log_error(error_detail, original_error)\n\n        self.logger.warning.assert_called_once()\n        log_message = self.logger.warning.call_args[0][0]\n        self.assertIn(\"[timeout_error]\", log_message)\n        self.assertIn(\"Simple timeout error\", log_message)\n        self.assertNotIn(\"Details:\", log_message)\n\n    def test_011_create_acme_error_response_validation_error(self):\n        \"\"\"Test creating ACME error response for validation error\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Invalid challenge response\",\n            suggestion=\"Check token format\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail, 400)\n\n        expected_response = {\n            \"code\": 400,\n            \"type\": \"urn:ietf:params:acme:error:incorrectResponse\",\n            \"detail\": \"Invalid challenge response Suggestion: Check token format\",\n        }\n        self.assertEqual(response, expected_response)\n\n    def test_012_create_acme_error_response_network_error(self):\n        \"\"\"Test creating ACME error response for network error\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.NETWORK_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Connection failed\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail, 502)\n\n        expected_response = {\n            \"code\": 502,\n            \"type\": \"urn:ietf:params:acme:error:connection\",\n            \"detail\": \"Connection failed\",\n        }\n        self.assertEqual(response, expected_response)\n\n    def test_013_create_acme_error_response_malformed_request(self):\n        \"\"\"Test creating ACME error response for malformed request\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.MALFORMED_REQUEST,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Invalid JSON format\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail)\n\n        expected_response = {\n            \"code\": 400,\n            \"type\": \"urn:ietf:params:acme:error:malformed\",\n            \"detail\": \"Invalid JSON format\",\n        }\n        self.assertEqual(response, expected_response)\n\n    def test_014_create_acme_error_response_authentication_error(self):\n        \"\"\"Test creating ACME error response for authentication error\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.AUTHENTICATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Invalid credentials\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail, 401)\n\n        expected_response = {\n            \"code\": 401,\n            \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n            \"detail\": \"Invalid credentials\",\n        }\n        self.assertEqual(response, expected_response)\n\n    def test_015_create_acme_error_response_timeout_error(self):\n        \"\"\"Test creating ACME error response for timeout error\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.TIMEOUT_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Request timeout\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail, 408)\n\n        expected_response = {\n            \"code\": 408,\n            \"type\": \"urn:ietf:params:acme:error:connection\",\n            \"detail\": \"Request timeout\",\n        }\n        self.assertEqual(response, expected_response)\n\n    def test_016_create_acme_error_response_server_errors(self):\n        \"\"\"Test creating ACME error response for server-side errors\"\"\"\n        for category in [\n            ErrorCategory.CONFIGURATION_ERROR,\n            ErrorCategory.DATABASE_ERROR,\n            ErrorCategory.UNKNOWN_ERROR,\n        ]:\n            with self.subTest(category=category):\n                error_detail = ErrorDetail(\n                    category=category,\n                    severity=ErrorSeverity.HIGH,\n                    message=\"Server error\",\n                )\n\n                response = self.error_handler.create_acme_error_response(\n                    error_detail, 500\n                )\n\n                expected_response = {\n                    \"code\": 500,\n                    \"type\": \"urn:ietf:params:acme:error:serverInternal\",\n                    \"detail\": \"Server error\",\n                }\n                self.assertEqual(response, expected_response)\n\n    def test_017_create_acme_error_response_without_suggestion(self):\n        \"\"\"Test creating ACME error response without suggestion\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Simple validation error\",\n        )\n\n        response = self.error_handler.create_acme_error_response(error_detail)\n\n        expected_response = {\n            \"code\": 400,\n            \"type\": \"urn:ietf:params:acme:error:incorrectResponse\",\n            \"detail\": \"Simple validation error\",\n        }\n        self.assertEqual(response, expected_response)\n\n\nclass TestErrorRecovery(unittest.TestCase):\n    \"\"\"Test cases for ErrorRecovery class\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for ErrorRecovery tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.error_recovery = ErrorRecovery(self.logger)\n\n    def test_001_error_recovery_initialization(self):\n        \"\"\"Test ErrorRecovery initialization\"\"\"\n        self.assertEqual(self.error_recovery.logger, self.logger)\n\n    def test_002_should_retry_network_errors(self):\n        \"\"\"Test retry logic for network errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.NETWORK_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Network failure\",\n        )\n\n        # Should retry for attempts 1 and 2\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 1))\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 2))\n        # Should not retry after 3 attempts\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 3))\n\n    def test_003_should_retry_timeout_errors(self):\n        \"\"\"Test retry logic for timeout errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.TIMEOUT_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Timeout\",\n        )\n\n        # Should retry for attempts 1 and 2\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 1))\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 2))\n        # Should not retry after 3 attempts\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 3))\n\n    def test_004_should_retry_database_errors(self):\n        \"\"\"Test retry logic for database errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.DATABASE_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Database error\",\n        )\n\n        # Should retry for attempts 1 and 2\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 1))\n        self.assertTrue(self.error_recovery.should_retry(error_detail, 2))\n        # Should not retry after 3 attempts\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 3))\n\n    def test_005_should_not_retry_validation_errors(self):\n        \"\"\"Test no retry for validation errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.VALIDATION_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Validation failed\",\n        )\n\n        # Should never retry validation errors\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 1))\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 2))\n\n    def test_006_should_not_retry_malformed_requests(self):\n        \"\"\"Test no retry for malformed request errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.MALFORMED_REQUEST,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Malformed request\",\n        )\n\n        # Should never retry malformed requests\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 1))\n\n    def test_007_should_not_retry_authentication_errors(self):\n        \"\"\"Test no retry for authentication errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.AUTHENTICATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Authentication failed\",\n        )\n\n        # Should never retry authentication errors\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 1))\n\n    def test_008_should_not_retry_unknown_errors(self):\n        \"\"\"Test default no retry for unknown errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.UNKNOWN_ERROR,\n            severity=ErrorSeverity.MEDIUM,\n            message=\"Unknown error\",\n        )\n\n        # Should not retry unknown errors by default\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 1))\n\n    def test_009_should_not_retry_configuration_errors(self):\n        \"\"\"Test no retry for configuration errors\"\"\"\n        error_detail = ErrorDetail(\n            category=ErrorCategory.CONFIGURATION_ERROR,\n            severity=ErrorSeverity.HIGH,\n            message=\"Configuration error\",\n        )\n\n        # Should not retry configuration errors\n        self.assertFalse(self.error_recovery.should_retry(error_detail, 1))\n\n    def test_010_get_retry_delay_exponential_backoff(self):\n        \"\"\"Test exponential backoff delay calculation\"\"\"\n        # Test exponential backoff: 2^attempt_count\n        self.assertEqual(self.error_recovery.get_retry_delay(1), 2)  # 2^1\n        self.assertEqual(self.error_recovery.get_retry_delay(2), 4)  # 2^2\n        self.assertEqual(self.error_recovery.get_retry_delay(3), 8)  # 2^3\n        self.assertEqual(self.error_recovery.get_retry_delay(4), 16)  # 2^4\n\n    def test_011_get_retry_delay_max_cap(self):\n        \"\"\"Test retry delay maximum cap\"\"\"\n        # Test maximum cap of 30 seconds\n        self.assertEqual(\n            self.error_recovery.get_retry_delay(10), 30\n        )  # 2^10 = 1024, capped at 30\n        self.assertEqual(\n            self.error_recovery.get_retry_delay(20), 30\n        )  # Should still be capped\n\n    def test_012_get_retry_delay_zero_attempts(self):\n        \"\"\"Test retry delay with zero attempts\"\"\"\n        self.assertEqual(self.error_recovery.get_retry_delay(0), 1)  # 2^0 = 1\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_challenge_registry_setup.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unit tests for challenge_registry_setup.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport logging\nfrom unittest.mock import Mock, patch, call\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass MockConfig:\n    \"\"\"Mock configuration object for testing\"\"\"\n\n    def __init__(self, **kwargs):\n        self.email_identifier_support = kwargs.get(\"email_identifier_support\", False)\n        self.tnauthlist_support = kwargs.get(\"tnauthlist_support\", False)\n        self.forward_address_check = kwargs.get(\"forward_address_check\", False)\n        self.reverse_address_check = kwargs.get(\"reverse_address_check\", False)\n\n\nclass TestChallengeRegistrySetup(unittest.TestCase):\n    \"\"\"Test cases for challenge_registry_setup.py functions\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.config = MockConfig()\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_001_create_challenge_validator_registry_basic(self):\n        \"\"\"Test basic challenge validator registry creation with minimal config\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_http_instance = Mock()\n        mock_dns_instance = Mock()\n        mock_tls_instance = Mock()\n\n        mock_http_validator.return_value = mock_http_instance\n        mock_dns_validator.return_value = mock_dns_instance\n        mock_tls_validator.return_value = mock_tls_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            # Import and test\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with minimal config (no optional validators)\n            config = MockConfig(\n                email_identifier_support=False,\n                tnauthlist_support=False,\n                forward_address_check=False,\n                reverse_address_check=False,\n            )\n\n            result = create_challenge_validator_registry(self.logger, config)\n\n            # Verify registry creation\n            mock_registry.assert_called_once_with(self.logger)\n            self.assertEqual(result, mock_registry_instance)\n\n            # Verify standard validators registered\n            mock_http_validator.assert_called_once_with(self.logger)\n            mock_dns_validator.assert_called_once_with(self.logger)\n            mock_tls_validator.assert_called_once_with(self.logger)\n\n            expected_calls = [\n                call.register_validator(mock_http_instance),\n                call.register_validator(mock_dns_instance),\n                call.register_validator(mock_tls_instance),\n            ]\n            mock_registry_instance.register_validator.assert_has_calls(\n                expected_calls, any_order=True\n            )\n\n            # Verify optional validators NOT called\n            mock_email_validator.assert_not_called()\n            mock_tkauth_validator.assert_not_called()\n            mock_source_validator.assert_called_once()\n\n            # Verify logging\n            self.logger.debug.assert_has_calls(\n                [\n                    call(\n                        \"challenge_registry_setup.create_challenge_validator_registry()\"\n                    ),\n                    call(\n                        \"create_challenge_validator_registry(): Registry created with %d validators: %s\",\n                        3,\n                        \"http-01, dns-01, tls-alpn-01\",\n                    ),\n                    call(\n                        \"challenge_registry_setup.create_challenge_validator_registry() ended\"\n                    ),\n                ]\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_002_create_challenge_validator_registry_email_support(self):\n        \"\"\"Test registry creation with email identifier support enabled\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"email-reply-00\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_email_instance = Mock()\n        mock_email_validator.return_value = mock_email_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with email support enabled\n            config = MockConfig(email_identifier_support=True)\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify email validator registered\n            mock_email_validator.assert_called_once_with(self.logger)\n            mock_registry_instance.register_validator.assert_any_call(\n                mock_email_instance\n            )\n\n            # Verify tkauth and source validators NOT called\n            mock_tkauth_validator.assert_not_called()\n            mock_source_validator.assert_called_once()\n            mock_http_validator.assert_called_once()\n            mock_dns_validator.assert_called_once()\n            mock_tls_validator.assert_called_once()\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_003_create_challenge_validator_registry_all_enabled(self):\n        \"\"\"Test registry creation with all optional features enabled\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"email-reply-00\",\n            \"tkauth-01\",\n            \"source-address\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_http_instance = Mock()\n        mock_dns_instance = Mock()\n        mock_tls_instance = Mock()\n        mock_email_instance = Mock()\n        mock_tkauth_instance = Mock()\n        mock_source_instance = Mock()\n\n        mock_http_validator.return_value = mock_http_instance\n        mock_dns_validator.return_value = mock_dns_instance\n        mock_tls_validator.return_value = mock_tls_instance\n        mock_email_validator.return_value = mock_email_instance\n        mock_tkauth_validator.return_value = mock_tkauth_instance\n        mock_source_validator.return_value = mock_source_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with all features enabled\n            config = MockConfig(\n                email_identifier_support=True,\n                tnauthlist_support=True,\n                forward_address_check=True,\n                reverse_address_check=True,\n            )\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify all validators created and registered\n            mock_http_validator.assert_called_once_with(self.logger)\n            mock_dns_validator.assert_called_once_with(self.logger)\n            mock_tls_validator.assert_called_once_with(self.logger)\n            mock_email_validator.assert_called_once_with(self.logger)\n            mock_tkauth_validator.assert_called_once_with(self.logger)\n            mock_source_validator.assert_called_once_with(\n                self.logger, forward_check=True, reverse_check=True\n            )\n\n            expected_calls = [\n                call.register_validator(mock_http_instance),\n                call.register_validator(mock_dns_instance),\n                call.register_validator(mock_tls_instance),\n                call.register_validator(mock_email_instance),\n                call.register_validator(mock_tkauth_instance),\n                call.register_validator(mock_source_instance),\n            ]\n            mock_registry_instance.register_validator.assert_has_calls(\n                expected_calls, any_order=True\n            )\n\n            # Verify logging - the function uses debug(), not info()\n            self.logger.debug.assert_has_calls(\n                [\n                    call(\n                        \"challenge_registry_setup.create_challenge_validator_registry()\"\n                    ),\n                    call(\n                        \"create_challenge_validator_registry(): Registry created with %d validators: %s\",\n                        6,\n                        \"http-01, dns-01, tls-alpn-01, email-reply-00, tkauth-01, source-address\",\n                    ),\n                    call(\n                        \"challenge_registry_setup.create_challenge_validator_registry() ended\"\n                    ),\n                ]\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_004_create_challenge_validator_registry_none_config(self):\n        \"\"\"Test registry creation with None config\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with None config - should handle gracefully\n            try:\n                create_challenge_validator_registry(self.logger, None)\n                # This should raise an AttributeError since None.email_identifier_support would fail\n                self.fail(\"Expected AttributeError for None config\")\n            except AttributeError:\n                # Expected behavior\n                pass\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_005_create_challenge_validator_registry_registry_exception(self):\n        \"\"\"Test registry creation when registry constructor raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry.side_effect = Exception(\"Registry creation failed\")\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            config = MockConfig()\n\n            with self.assertRaises(Exception) as context:\n                create_challenge_validator_registry(self.logger, config)\n\n            self.assertEqual(str(context.exception), \"Registry creation failed\")\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_006_create_custom_registry_basic(self):\n        \"\"\"Test basic custom registry creation\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\"mock-01\", \"mock-02\"]\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            # Create mock validator classes\n            mock_validator_class1 = Mock()\n            mock_validator_class2 = Mock()\n            mock_validator1 = Mock()\n            mock_validator2 = Mock()\n            mock_validator_class1.return_value = mock_validator1\n            mock_validator_class2.return_value = mock_validator2\n\n            validator_classes = [mock_validator_class1, mock_validator_class2]\n\n            result = create_custom_registry(self.logger, validator_classes)\n\n            # Verify registry creation\n            mock_registry.assert_called_once_with(self.logger)\n            self.assertEqual(result, mock_registry_instance)\n\n            # Verify validators created and registered\n            mock_validator_class1.assert_called_once_with(self.logger)\n            mock_validator_class2.assert_called_once_with(self.logger)\n\n            expected_calls = [\n                call.register_validator(mock_validator1),\n                call.register_validator(mock_validator2),\n            ]\n            mock_registry_instance.register_validator.assert_has_calls(\n                expected_calls, any_order=True\n            )\n\n            # Verify logging\n            self.logger.info.assert_called_once()\n            info_call_args = self.logger.info.call_args[0]\n            self.assertIn(\n                \"Custom challenge validator registry created with 2 validators\",\n                info_call_args[0] % 2,\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_007_create_custom_registry_empty_validators(self):\n        \"\"\"Test custom registry creation with empty validator list\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = []\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            validator_classes = []\n\n            result = create_custom_registry(self.logger, validator_classes)\n\n            # Verify registry creation\n            mock_registry.assert_called_once_with(self.logger)\n            self.assertEqual(result, mock_registry_instance)\n\n            # Verify no validators registered\n            mock_registry_instance.register_validator.assert_not_called()\n\n            # Verify logging\n            self.logger.info.assert_called_once()\n            info_call_args = self.logger.info.call_args[0]\n            self.assertIn(\n                \"Custom challenge validator registry created with 0 validators\",\n                info_call_args[0] % 0,\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_008_create_custom_registry_none_validator_classes(self):\n        \"\"\"Test custom registry creation with None validator classes\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            # Test with None validator classes - should raise TypeError\n            with self.assertRaises(TypeError):\n                create_custom_registry(self.logger, None)\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_009_create_custom_registry_validator_exception(self):\n        \"\"\"Test custom registry creation when validator constructor raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            mock_validator_class = Mock()\n            mock_validator_class.side_effect = Exception(\"Validator creation failed\")\n\n            validator_classes = [mock_validator_class]\n\n            with self.assertRaises(Exception) as context:\n                create_custom_registry(self.logger, validator_classes)\n\n            self.assertEqual(str(context.exception), \"Validator creation failed\")\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_010_create_custom_registry_registration_exception(self):\n        \"\"\"Test custom registry creation when validator registration raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.register_validator.side_effect = Exception(\n            \"Registration failed\"\n        )\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            mock_validator_class = Mock()\n            mock_validator = Mock()\n            mock_validator_class.return_value = mock_validator\n\n            validator_classes = [mock_validator_class]\n\n            with self.assertRaises(Exception) as context:\n                create_custom_registry(self.logger, validator_classes)\n\n            self.assertEqual(str(context.exception), \"Registration failed\")\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_011_create_challenge_validator_registry_tnauthlist_support(self):\n        \"\"\"Test registry creation with tnauthlist support enabled\"\"\"\n\n        # Mock the module's imports\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"tkauth-01\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_tkauth_instance = Mock()\n        mock_tkauth_validator.return_value = mock_tkauth_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with tnauthlist support enabled\n            config = MockConfig(tnauthlist_support=True)\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify tkauth validator registered\n            mock_tkauth_validator.assert_called_once_with(self.logger)\n            mock_registry_instance.register_validator.assert_any_call(\n                mock_tkauth_instance\n            )\n\n            # Verify email and source validators NOT called\n            mock_email_validator.assert_not_called()\n            mock_source_validator.assert_called_once()\n            mock_http_validator.assert_called_once()\n            mock_dns_validator.assert_called_once()\n            mock_tls_validator.assert_called_once()\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_012_create_challenge_validator_registry_forward_address_check(self):\n        \"\"\"Test registry creation with forward address checking enabled\"\"\"\n\n        # Mock the module's imports\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"source-address\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_source_instance = Mock()\n        mock_source_validator.return_value = mock_source_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with forward address check enabled\n            config = MockConfig(forward_address_check=True)\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify source address validator registered with correct parameters\n            mock_source_validator.assert_called_once_with(\n                self.logger, forward_check=True, reverse_check=False\n            )\n            mock_registry_instance.register_validator.assert_any_call(\n                mock_source_instance\n            )\n\n            # Verify email and tkauth validators NOT called\n            mock_email_validator.assert_not_called()\n            mock_tkauth_validator.assert_not_called()\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_013_create_challenge_validator_registry_reverse_address_check(self):\n        \"\"\"Test registry creation with reverse address checking enabled\"\"\"\n\n        # Mock the module's imports\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"source-address\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_source_instance = Mock()\n        mock_source_validator.return_value = mock_source_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with reverse address check enabled\n            config = MockConfig(reverse_address_check=True)\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify source address validator registered with correct parameters\n            mock_source_validator.assert_called_once_with(\n                self.logger, forward_check=False, reverse_check=True\n            )\n            mock_registry_instance.register_validator.assert_any_call(\n                mock_source_instance\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_014_create_challenge_validator_registry_both_address_checks(self):\n        \"\"\"Test registry creation with both forward and reverse address checking enabled\"\"\"\n\n        # Mock the module's imports\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"dns-01\",\n            \"tls-alpn-01\",\n            \"source-address\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_source_instance = Mock()\n        mock_source_validator.return_value = mock_source_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Test with both address checks enabled\n            config = MockConfig(forward_address_check=True, reverse_address_check=True)\n\n            create_challenge_validator_registry(self.logger, config)\n\n            # Verify source address validator registered with both checks\n            mock_source_validator.assert_called_once_with(\n                self.logger, forward_check=True, reverse_check=True\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_015_create_custom_registry_with_config(self):\n        \"\"\"Test custom registry creation with config parameter\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\"mock-01\"]\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            mock_validator_class = Mock()\n            mock_validator = Mock()\n            mock_validator_class.return_value = mock_validator\n\n            validator_classes = [mock_validator_class]\n            config = {\"test\": \"value\"}\n\n            create_custom_registry(self.logger, validator_classes, config)\n\n            # Verify registry creation (config not used in current implementation)\n            mock_registry.assert_called_once_with(self.logger)\n            mock_validator_class.assert_called_once_with(self.logger)\n            mock_registry_instance.register_validator.assert_called_once_with(\n                mock_validator\n            )\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_016_create_custom_registry_get_supported_types_exception(self):\n        \"\"\"Test custom registry creation when get_supported_types raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.side_effect = Exception(\n            \"Get types failed\"\n        )\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            mock_validator_class = Mock()\n            mock_validator = Mock()\n            mock_validator_class.return_value = mock_validator\n\n            validator_classes = [mock_validator_class]\n\n            with self.assertRaises(Exception) as context:\n                create_custom_registry(self.logger, validator_classes)\n\n            self.assertEqual(str(context.exception), \"Get types failed\")\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_017_create_custom_registry_mixed_validator_types(self):\n        \"\"\"Test custom registry creation with different validator types\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.get_supported_types.return_value = [\n            \"http-01\",\n            \"custom-01\",\n            \"test-01\",\n        ]\n        mock_registry.return_value = mock_registry_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\", ChallengeValidatorRegistry=mock_registry\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n        ):\n\n            from acme_srv.challenge_registry_setup import create_custom_registry\n\n            # Create different types of mock validator classes\n            class MockHttpValidator:\n                def __init__(self, logger):\n                    self.logger = logger\n\n            class MockCustomValidator:\n                def __init__(self, logger):\n                    self.logger = logger\n\n            mock_test_validator_class = Mock()\n            mock_test_validator = Mock()\n            mock_test_validator_class.return_value = mock_test_validator\n\n            validator_classes = [\n                MockHttpValidator,\n                MockCustomValidator,\n                mock_test_validator_class,\n            ]\n\n            result = create_custom_registry(self.logger, validator_classes)\n\n            # Verify registry creation\n            self.assertEqual(result, mock_registry_instance)\n\n            # Verify all validator types were handled\n            self.assertEqual(mock_registry_instance.register_validator.call_count, 3)\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_018_create_challenge_validator_registry_validator_exception(self):\n        \"\"\"Test registry creation when validator constructor raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_http_validator.side_effect = Exception(\"Validator creation failed\")\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            config = MockConfig()\n\n            with self.assertRaises(Exception) as context:\n                create_challenge_validator_registry(self.logger, config)\n\n            self.assertEqual(str(context.exception), \"Validator creation failed\")\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_019_create_challenge_validator_registry_missing_config_attributes(self):\n        \"\"\"Test registry creation with config missing some attributes\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            # Create config with missing attributes\n            class PartialConfig:\n                email_identifier_support = False\n                # Missing tnauthlist_support, forward_address_check, reverse_address_check\n\n            config = PartialConfig()\n\n            # Should raise AttributeError when accessing missing attributes\n            with self.assertRaises(AttributeError):\n                create_challenge_validator_registry(self.logger, config)\n\n    @patch.dict(\n        \"sys.modules\",\n        {\n            \"OpenSSL\": Mock(),\n            \"OpenSSL.crypto\": Mock(),\n            \"acme_srv.helper\": Mock(),\n            \"acme_srv.helpers.certificates\": Mock(),\n            \"acme_srv.challenge_validators\": Mock(),\n        },\n    )\n    def test_020_create_challenge_validator_registry_registration_exception(self):\n        \"\"\"Test registry creation when validator registration raises exception\"\"\"\n\n        # Mock all the validator classes\n        mock_registry = Mock()\n        mock_registry_instance = Mock()\n        mock_registry_instance.register_validator.side_effect = Exception(\n            \"Registration failed\"\n        )\n        mock_registry.return_value = mock_registry_instance\n\n        mock_http_validator = Mock()\n        mock_dns_validator = Mock()\n        mock_tls_validator = Mock()\n        mock_email_validator = Mock()\n        mock_tkauth_validator = Mock()\n        mock_source_validator = Mock()\n\n        mock_http_instance = Mock()\n        mock_http_validator.return_value = mock_http_instance\n\n        # Patch both the source module and the target module where imports are used\n        with patch.multiple(\n            \"acme_srv.challenge_validators\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ), patch.multiple(\n            \"acme_srv.challenge_registry_setup\",\n            ChallengeValidatorRegistry=mock_registry,\n            HttpChallengeValidator=mock_http_validator,\n            DnsChallengeValidator=mock_dns_validator,\n            TlsAlpnChallengeValidator=mock_tls_validator,\n            EmailReplyChallengeValidator=mock_email_validator,\n            TkauthChallengeValidator=mock_tkauth_validator,\n            SourceAddressValidator=mock_source_validator,\n        ):\n\n            from acme_srv.challenge_registry_setup import (\n                create_challenge_validator_registry,\n            )\n\n            config = MockConfig()\n\n            with self.assertRaises(Exception) as context:\n                create_challenge_validator_registry(self.logger, config)\n\n            self.assertEqual(str(context.exception), \"Registration failed\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_challenge_validators.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unit tests for challenge_validators package\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport logging\nfrom unittest.mock import Mock, patch\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n# Import the modules under test\nfrom acme_srv.challenge_validators.base import (\n    ValidationResult,\n    ChallengeContext,\n    ChallengeValidator,\n    ChallengeValidationError,\n    ValidationTimeoutError,\n    InvalidChallengeTypeError,\n)\nfrom acme_srv.challenge_validators.registry import ChallengeValidatorRegistry\nfrom acme_srv.challenge_validators.http_validator import HttpChallengeValidator\nfrom acme_srv.challenge_validators.dns_validator import DnsChallengeValidator\nfrom acme_srv.challenge_validators.tls_alpn_validator import TlsAlpnChallengeValidator\nfrom acme_srv.challenge_validators.email_reply_validator import (\n    EmailReplyChallengeValidator,\n)\nfrom acme_srv.challenge_validators.tkauth_validator import TkauthChallengeValidator\nfrom acme_srv.challenge_validators.source_address_validator import (\n    SourceAddressValidator,\n)\n\n\nclass TestValidationResult(unittest.TestCase):\n    \"\"\"Test cases for ValidationResult dataclass\"\"\"\n\n    def test_001_validation_result_creation_minimal(self):\n        \"\"\"Test ValidationResult creation with minimal parameters\"\"\"\n        result = ValidationResult(success=True, invalid=False)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n        self.assertIsNone(result.details)\n\n    def test_002_validation_result_creation_full(self):\n        \"\"\"Test ValidationResult creation with all parameters\"\"\"\n        details = {\"key\": \"value\", \"count\": 42}\n        result = ValidationResult(\n            success=False, invalid=True, error_message=\"Test error\", details=details\n        )\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(result.error_message, \"Test error\")\n        self.assertEqual(result.details, details)\n\n    def test_003_validation_result_dataclass_behavior(self):\n        \"\"\"Test ValidationResult dataclass behavior\"\"\"\n        result1 = ValidationResult(success=True, invalid=False)\n        result2 = ValidationResult(success=True, invalid=False)\n        result3 = ValidationResult(success=False, invalid=True)\n\n        # Test equality\n        self.assertEqual(result1, result2)\n        self.assertNotEqual(result1, result3)\n\n        # Test string representation\n        self.assertIn(\"ValidationResult\", str(result1))\n\n\nclass TestChallengeContext(unittest.TestCase):\n    \"\"\"Test cases for ChallengeContext dataclass\"\"\"\n\n    def test_001_challenge_context_creation_minimal(self):\n        \"\"\"Test ChallengeContext creation with required parameters\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test_challenge\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumbprint\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        self.assertEqual(context.challenge_name, \"test_challenge\")\n        self.assertEqual(context.token, \"test_token\")\n        self.assertEqual(context.jwk_thumbprint, \"test_thumbprint\")\n        self.assertEqual(context.authorization_type, \"dns\")\n        self.assertEqual(context.authorization_value, \"example.com\")\n        # Test default values\n        self.assertIsNone(context.keyauthorization)\n        self.assertIsNone(context.dns_servers)\n        self.assertIsNone(context.proxy_servers)\n        self.assertEqual(context.timeout, 10)\n        self.assertIsNone(context.source_address)\n\n    def test_002_challenge_context_creation_full(self):\n        \"\"\"Test ChallengeContext creation with all parameters\"\"\"\n        dns_servers = [\"8.8.8.8\", \"1.1.1.1\"]\n        proxy_servers = {\"http\": \"http://proxy.example.com:8080\"}\n\n        context = ChallengeContext(\n            challenge_name=\"full_challenge\",\n            token=\"full_token\",\n            jwk_thumbprint=\"full_thumbprint\",\n            authorization_type=\"ip\",\n            authorization_value=\"192.168.1.1\",\n            keyauthorization=\"test_keyauth\",\n            dns_servers=dns_servers,\n            proxy_servers=proxy_servers,\n            timeout=30,\n            source_address=\"192.168.1.100\",\n        )\n\n        self.assertEqual(context.challenge_name, \"full_challenge\")\n        self.assertEqual(context.token, \"full_token\")\n        self.assertEqual(context.jwk_thumbprint, \"full_thumbprint\")\n        self.assertEqual(context.authorization_type, \"ip\")\n        self.assertEqual(context.authorization_value, \"192.168.1.1\")\n        self.assertEqual(context.keyauthorization, \"test_keyauth\")\n        self.assertEqual(context.dns_servers, dns_servers)\n        self.assertEqual(context.proxy_servers, proxy_servers)\n        self.assertEqual(context.timeout, 30)\n        self.assertEqual(context.source_address, \"192.168.1.100\")\n\n    def test_003_challenge_context_dataclass_behavior(self):\n        \"\"\"Test ChallengeContext dataclass behavior\"\"\"\n        context1 = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context2 = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context3 = ChallengeContext(\n            challenge_name=\"different\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # Test equality\n        self.assertEqual(context1, context2)\n        self.assertNotEqual(context1, context3)\n\n        # Test string representation\n        self.assertIn(\"ChallengeContext\", str(context1))\n\n\nclass TestChallengeValidationExceptions(unittest.TestCase):\n    \"\"\"Test cases for challenge validation exceptions\"\"\"\n\n    def test_001_challenge_validation_error(self):\n        \"\"\"Test ChallengeValidationError exception\"\"\"\n        error = ChallengeValidationError(\"Test validation error\")\n\n        self.assertIsInstance(error, Exception)\n        self.assertEqual(str(error), \"Test validation error\")\n\n        # Test it can be raised and caught\n        with self.assertRaises(ChallengeValidationError) as context:\n            raise error\n        self.assertEqual(str(context.exception), \"Test validation error\")\n\n    def test_002_validation_timeout_error(self):\n        \"\"\"Test ValidationTimeoutError exception\"\"\"\n        error = ValidationTimeoutError(\"Timeout occurred\")\n\n        self.assertIsInstance(error, ChallengeValidationError)\n        self.assertIsInstance(error, Exception)\n        self.assertEqual(str(error), \"Timeout occurred\")\n\n    def test_003_invalid_challenge_type_error(self):\n        \"\"\"Test InvalidChallengeTypeError exception\"\"\"\n        error = InvalidChallengeTypeError(\"Unsupported challenge type\")\n\n        self.assertIsInstance(error, ChallengeValidationError)\n        self.assertIsInstance(error, Exception)\n        self.assertEqual(str(error), \"Unsupported challenge type\")\n\n\nclass TestChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for ChallengeValidator abstract base class\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for ChallengeValidator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n\n    def test_001_challenge_validator_abstract(self):\n        \"\"\"Test ChallengeValidator is abstract and cannot be instantiated\"\"\"\n        with self.assertRaises(TypeError):\n            ChallengeValidator(self.logger)\n\n    def test_002_challenge_validator_validate_challenge_success(self):\n        \"\"\"Test validate_challenge method with successful validation\"\"\"\n        # Create a concrete implementation for testing\n        class TestValidator(ChallengeValidator):\n            def get_challenge_type(self):\n                return \"test-01\"\n\n            def perform_validation(self, context):\n                return ValidationResult(success=True, invalid=False)\n\n        validator = TestValidator(self.logger)\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = validator.validate_challenge(context)\n\n        self.assertIsInstance(result, ValidationResult)\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n\n        # Verify logging calls\n        self.logger.debug.assert_called()\n\n    def test_003_challenge_validator_validate_challenge_exception(self):\n        \"\"\"Test validate_challenge method with exception handling\"\"\"\n        # Create a concrete implementation that raises an exception\n        class FailingValidator(ChallengeValidator):\n            def get_challenge_type(self):\n                return \"failing-01\"\n\n            def perform_validation(self, context):\n                raise ValueError(\"Test validation error\")\n\n        validator = FailingValidator(self.logger)\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = validator.validate_challenge(context)\n\n        self.assertIsInstance(result, ValidationResult)\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(result.error_message, \"Test validation error\")\n        self.assertEqual(result.details[\"exception_type\"], \"ValueError\")\n\n        # Verify error logging\n        self.logger.error.assert_called()\n\n\nclass TestChallengeValidatorRegistry(unittest.TestCase):\n    \"\"\"Test cases for ChallengeValidatorRegistry\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for registry tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.registry = ChallengeValidatorRegistry(self.logger)\n\n    def test_001_registry_initialization(self):\n        \"\"\"Test registry initialization\"\"\"\n        self.assertEqual(self.registry.logger, self.logger)\n        self.assertEqual(self.registry._validators, {})\n\n    def test_002_register_validator(self):\n        \"\"\"Test registering a validator\"\"\"\n        # Create a mock validator\n        mock_validator = Mock(spec=ChallengeValidator)\n        mock_validator.get_challenge_type.return_value = \"test-01\"\n\n        self.registry.register_validator(mock_validator)\n\n        # Check validator was registered\n        self.assertIn(\"test-01\", self.registry._validators)\n        self.assertEqual(self.registry._validators[\"test-01\"], mock_validator)\n\n        # Verify logging\n        self.logger.debug.assert_called()\n\n    def test_003_get_validator_existing(self):\n        \"\"\"Test getting an existing validator\"\"\"\n        # Create and register a mock validator\n        mock_validator = Mock(spec=ChallengeValidator)\n        mock_validator.get_challenge_type.return_value = \"test-01\"\n        self.registry.register_validator(mock_validator)\n\n        # Get the validator\n        result = self.registry.get_validator(\"test-01\")\n\n        self.assertEqual(result, mock_validator)\n        self.logger.debug.assert_called()\n\n    def test_004_get_validator_non_existing(self):\n        \"\"\"Test getting a non-existing validator\"\"\"\n        result = self.registry.get_validator(\"non-existent\")\n\n        self.assertIsNone(result)\n        self.logger.debug.assert_called()\n\n    def test_005_get_supported_types_empty(self):\n        \"\"\"Test getting supported types from empty registry\"\"\"\n        result = self.registry.get_supported_types()\n\n        self.assertEqual(result, [])\n        self.logger.debug.assert_called()\n\n    def test_006_get_supported_types_with_validators(self):\n        \"\"\"Test getting supported types with registered validators\"\"\"\n        # Register multiple validators\n        for challenge_type in [\"http-01\", \"dns-01\", \"tls-alpn-01\"]:\n            mock_validator = Mock(spec=ChallengeValidator)\n            mock_validator.get_challenge_type.return_value = challenge_type\n            self.registry.register_validator(mock_validator)\n\n        result = self.registry.get_supported_types()\n\n        self.assertEqual(set(result), {\"http-01\", \"dns-01\", \"tls-alpn-01\"})\n        self.logger.debug.assert_called()\n\n    def test_007_is_supported_true(self):\n        \"\"\"Test is_supported with supported challenge type\"\"\"\n        # Register a validator\n        mock_validator = Mock(spec=ChallengeValidator)\n        mock_validator.get_challenge_type.return_value = \"test-01\"\n        self.registry.register_validator(mock_validator)\n\n        result = self.registry.is_supported(\"test-01\")\n\n        self.assertTrue(result)\n        self.logger.debug.assert_called()\n\n    def test_008_is_supported_false(self):\n        \"\"\"Test is_supported with unsupported challenge type\"\"\"\n        result = self.registry.is_supported(\"non-existent\")\n\n        self.assertFalse(result)\n        self.logger.debug.assert_called()\n\n    def test_009_validate_challenge_success(self):\n        \"\"\"Test validate_challenge with supported challenge type\"\"\"\n        # Create a mock validator with validation result\n        mock_validator = Mock(spec=ChallengeValidator)\n        mock_validator.get_challenge_type.return_value = \"test-01\"\n        validation_result = ValidationResult(success=True, invalid=False)\n        mock_validator.validate_challenge.return_value = validation_result\n\n        self.registry.register_validator(mock_validator)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.registry.validate_challenge(\"test-01\", context)\n\n        self.assertEqual(result, validation_result)\n        mock_validator.validate_challenge.assert_called_once_with(context)\n        self.logger.debug.assert_called()\n\n    def test_010_validate_challenge_unsupported_type(self):\n        \"\"\"Test validate_challenge with unsupported challenge type\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"token\",\n            jwk_thumbprint=\"thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        with self.assertRaises(InvalidChallengeTypeError) as cm:\n            self.registry.validate_challenge(\"unsupported\", context)\n\n        self.assertIn(\"Unsupported challenge type: unsupported\", str(cm.exception))\n        self.logger.debug.assert_called()\n\n    def test_011_register_multiple_validators_same_type(self):\n        \"\"\"Test registering multiple validators for the same type overwrites\"\"\"\n        # Create two validators for the same type\n        validator1 = Mock(spec=ChallengeValidator)\n        validator1.get_challenge_type.return_value = \"test-01\"\n        validator2 = Mock(spec=ChallengeValidator)\n        validator2.get_challenge_type.return_value = \"test-01\"\n\n        # Register both\n        self.registry.register_validator(validator1)\n        self.registry.register_validator(validator2)\n\n        # The second should overwrite the first\n        result = self.registry.get_validator(\"test-01\")\n        self.assertEqual(result, validator2)\n        self.assertNotEqual(result, validator1)\n\n\nclass TestHttpChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for HttpChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for HTTP validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = HttpChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"http-01\")\n\n    def test_002_perform_validation_import_error(self):\n        \"\"\"Test perform_validation with import error\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # Mock the import to raise ImportError\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.helper\" or (\n                    len(args) > 0 and \"acme_srv.helper\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertIn(\"Required dependencies not available\", result.error_message)\n            self.assertIn(\"import_error\", result.details)\n            self.assertIn(\"import_error\", result.details)\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.url_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_003_perform_validation_dns_success(\n        self, mock_proxy_check, mock_url_get, mock_fqdn_resolve\n    ):\n        \"\"\"Test successful DNS-based HTTP validation\"\"\"\n        # Setup mocks\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_proxy_check.return_value = None\n        expected_response = \"test_token.test_thumb\"\n        mock_url_get.return_value = (expected_response, 200, None)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            timeout=10,\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n        self.assertEqual(result.details[\"expected\"], expected_response)\n        self.assertEqual(result.details[\"received\"], expected_response)\n\n        # Verify function calls\n        mock_fqdn_resolve.assert_called_once_with(self.logger, \"example.com\", None)\n        mock_url_get.assert_called_once_with(\n            self.logger,\n            \"http://example.com/.well-known/acme-challenge/test_token\",\n            dns_server_list=None,\n            proxy_server=None,\n            verify=False,\n            timeout=10,\n        )\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_004_perform_validation_dns_resolution_failed(self, mock_fqdn_resolve):\n        \"\"\"Test HTTP validation with DNS resolution failure\"\"\"\n        mock_fqdn_resolve.return_value = ([], True, \"NXDOMAIN: test.com does not exist\")\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"invalid.example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:dns\", \"detail\": \"DNS resolution failed: NXDOMAIN: test.com does not exist\"}',\n        )\n        self.assertEqual(result.details[\"fqdn\"], \"invalid.example.com\")\n\n    @patch(\"acme_srv.helper.ip_validate\")\n    @patch(\"acme_srv.helper.url_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_005_perform_validation_ip_success(\n        self, mock_proxy_check, mock_url_get, mock_ip_validate\n    ):\n        \"\"\"Test successful IP-based HTTP validation\"\"\"\n        # Setup mocks\n        mock_ip_validate.return_value = (\"192.168.1.1\", False)\n        mock_proxy_check.return_value = None\n        expected_response = \"test_token.test_thumb\"\n        mock_url_get.return_value = (expected_response, 200, None)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"ip\",\n            authorization_value=\"192.168.1.1\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n\n        # Verify function calls\n        mock_ip_validate.assert_called_once_with(self.logger, \"192.168.1.1\")\n\n    @patch(\"acme_srv.helper.ip_validate\")\n    def test_006_perform_validation_invalid_ip(self, mock_ip_validate):\n        \"\"\"Test HTTP validation with invalid IP address\"\"\"\n        mock_ip_validate.return_value = (\"\", True)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"ip\",\n            authorization_value=\"invalid.ip\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:malformed\", \"detail\": \"Invalid IP address: invalid.ip\"}',\n        )\n        self.assertEqual(result.details[\"ip\"], \"invalid.ip\")\n\n    def test_007_perform_validation_unsupported_authorization_type(self):\n        \"\"\"Test HTTP validation with unsupported authorization type\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"unsupported\",\n            authorization_value=\"test.example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:unsupported\", \"detail\": \"Unsupported authorization type: unsupported\"}',\n        )\n        self.assertEqual(result.details[\"type\"], \"unsupported\")\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.url_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_008_perform_validation_http_request_failed(\n        self, mock_proxy_check, mock_url_get, mock_fqdn_resolve\n    ):\n        \"\"\"Test HTTP validation with failed HTTP request\"\"\"\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_proxy_check.return_value = None\n        mock_url_get.return_value = (\n            None,\n            500,\n            \"Connection failed\",\n        )  # Simulate request failure\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertFalse(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 403, \"type\": \"urn:ietf:params:acme:error:connection\", \"detail\": \"HTTP request failed: 500 Connection failed\"}',\n        )\n        self.assertIn(\"url\", result.details)\n        self.assertEqual(\n            result.details[\"url\"],\n            \"http://example.com/.well-known/acme-challenge/test_token\",\n        )\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.url_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_009_perform_validation_response_mismatch(\n        self, mock_proxy_check, mock_url_get, mock_fqdn_resolve\n    ):\n        \"\"\"Test HTTP validation with response mismatch\"\"\"\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_proxy_check.return_value = None\n        mock_url_get.return_value = (\"wrong_response\\nmore_content\", 200, None)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 403, \"type\": \"urn:ietf:params:acme:error:incorrectResponse\", \"detail\": \"Keyauthorization mismatch\"}',\n        )\n        self.assertEqual(result.details[\"expected\"], \"test_token.test_thumb\")\n        self.assertEqual(result.details[\"received\"], \"wrong_response\")\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.url_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_010_perform_validation_with_proxy(\n        self, mock_proxy_check, mock_url_get, mock_fqdn_resolve\n    ):\n        \"\"\"Test HTTP validation with proxy server\"\"\"\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_proxy_check.return_value = \"http://proxy.example.com:8080\"\n        expected_response = \"test_token.test_thumb\"\n        mock_url_get.return_value = (expected_response, 200, None)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            proxy_servers={\"http\": \"http://proxy.example.com:8080\"},\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n\n        # Verify proxy_check was called\n        mock_proxy_check.assert_called_once_with(\n            self.logger, \"example.com\", {\"http\": \"http://proxy.example.com:8080\"}\n        )\n\n        # Verify url_get was called with proxy\n        mock_url_get.assert_called_once_with(\n            self.logger,\n            \"http://example.com/.well-known/acme-challenge/test_token\",\n            dns_server_list=None,\n            proxy_server=\"http://proxy.example.com:8080\",\n            verify=False,\n            timeout=10,\n        )\n\n\nclass TestDnsChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for DnsChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for DNS validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = DnsChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"dns-01\")\n\n    def test_002_perform_validation_import_error(self):\n        \"\"\"Test perform_validation with import error\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # Mock the import to raise ImportError\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.helper\" or (\n                    len(args) > 0 and \"acme_srv.helper\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertIn(\"Required dependencies not available\", result.error_message)\n            self.assertIn(\"import_error\", result.details)\n\n    @patch(\"acme_srv.helper.txt_get\")\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    def test_003_perform_validation_basic_functionality(\n        self, mock_sha256, mock_b64_encode, mock_txt_get\n    ):\n        \"\"\"Test perform_validation basic functionality\"\"\"\n        # Mock all external calls to avoid actual DNS lookups\n        mock_sha256.return_value = b\"mocked_hash\"\n        mock_b64_encode.return_value = \"mocked_encoded_hash\"\n        mock_txt_get.return_value = []  # Empty DNS response\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # This should not crash and return a ValidationResult\n        result = self.validator.perform_validation(context)\n        self.assertIsInstance(result, ValidationResult)\n\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_004_perform_validation_success(\n        self, mock_txt_get, mock_sha256_hash, mock_b64_url_encode\n    ):\n        \"\"\"Test successful DNS validation\"\"\"\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"mocked_hash\"\n        mock_b64_url_encode.return_value = \"expected_hash\"\n        mock_txt_get.return_value = [\"expected_hash\", \"other_record\"]\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n        self.assertEqual(result.details[\"dns_record\"], \"_acme-challenge.example.com\")\n        self.assertEqual(result.details[\"expected_hash\"], \"expected_hash\")\n        self.assertEqual(\n            result.details[\"found_records\"], [\"expected_hash\", \"other_record\"]\n        )\n\n        # Verify function calls\n        mock_sha256_hash.assert_called_once_with(self.logger, \"test_token.test_thumb\")\n        mock_b64_url_encode.assert_called_once_with(self.logger, b\"mocked_hash\")\n        mock_txt_get.assert_called_once_with(\n            self.logger, \"_acme-challenge.example.com\", None\n        )\n\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_005_perform_validation_hash_not_found(\n        self, mock_txt_get, mock_sha256_hash, mock_b64_url_encode\n    ):\n        \"\"\"Test DNS validation when expected hash is not found\"\"\"\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"mocked_hash\"\n        mock_b64_url_encode.return_value = \"expected_hash\"\n        mock_txt_get.return_value = [\"wrong_hash\", \"other_record\"]\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 403, \"type\": \"urn:ietf:params:acme:error:incorrectResponse\", \"detail\": \"DNS record not found or incorrect\"}',\n        )\n        self.assertEqual(result.details[\"expected_hash\"], \"expected_hash\")\n        self.assertEqual(\n            result.details[\"found_records\"], [\"wrong_hash\", \"other_record\"]\n        )\n\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_006_perform_validation_wildcard_domain(\n        self, mock_txt_get, mock_sha256_hash, mock_b64_url_encode\n    ):\n        \"\"\"Test DNS validation with wildcard domain\"\"\"\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"mocked_hash\"\n        mock_b64_url_encode.return_value = \"expected_hash\"\n        mock_txt_get.return_value = [\"expected_hash\"]\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"*.example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n\n        # Verify that wildcard was handled correctly\n        mock_txt_get.assert_called_once_with(\n            self.logger, \"_acme-challenge.example.com\", None\n        )\n\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_007_perform_validation_with_dns_servers(\n        self, mock_txt_get, mock_sha256_hash, mock_b64_url_encode\n    ):\n        \"\"\"Test DNS validation with custom DNS servers\"\"\"\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"mocked_hash\"\n        mock_b64_url_encode.return_value = \"expected_hash\"\n        mock_txt_get.return_value = [\"expected_hash\"]\n\n        dns_servers = [\"8.8.8.8\", \"1.1.1.1\"]\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n            dns_servers=dns_servers,\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n\n        # Verify DNS servers were passed to txt_get\n        mock_txt_get.assert_called_once_with(\n            self.logger, \"_acme-challenge.example.com\", dns_servers\n        )\n\n    def test_008_handle_wildcard_domain_with_wildcard(self):\n        \"\"\"Test _handle_wildcard_domain with wildcard domain\"\"\"\n        result = self.validator._handle_wildcard_domain(\"*.example.com\")\n        self.assertEqual(result, \"example.com\")\n\n    def test_009_handle_wildcard_domain_without_wildcard(self):\n        \"\"\"Test _handle_wildcard_domain with regular domain\"\"\"\n        result = self.validator._handle_wildcard_domain(\"example.com\")\n        self.assertEqual(result, \"example.com\")\n\n    def test_010_handle_wildcard_domain_subdomain_wildcard(self):\n        \"\"\"Test _handle_wildcard_domain with subdomain wildcard\"\"\"\n        result = self.validator._handle_wildcard_domain(\"*.sub.example.com\")\n        self.assertEqual(result, \"sub.example.com\")\n\n    @patch(\"acme_srv.helper.b64_url_encode\")\n    @patch(\"acme_srv.helper.sha256_hash\")\n    @patch(\"acme_srv.helper.txt_get\")\n    def test_011_perform_validation_empty_dns_records(\n        self, mock_txt_get, mock_sha256_hash, mock_b64_url_encode\n    ):\n        \"\"\"Test DNS validation with empty DNS records\"\"\"\n        # Setup mocks\n        mock_sha256_hash.return_value = b\"mocked_hash\"\n        mock_b64_url_encode.return_value = \"expected_hash\"\n        mock_txt_get.return_value = []\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(result.details[\"found_records\"], [])\n\n\nclass TestTlsAlpnChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for TlsAlpnChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for TLS-ALPN validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = TlsAlpnChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"tls-alpn-01\")\n\n    def test_002_perform_validation_import_error(self):\n        \"\"\"Test perform_validation with import error\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # Mock the import to raise ImportError\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.helper\" or (\n                    len(args) > 0 and \"acme_srv.helper\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertIn(\"Required dependencies not available\", result.error_message)\n            self.assertIn(\"import_error\", result.details)\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_003_perform_validation_basic_functionality(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_fqdn_resolve,\n    ):\n        \"\"\"Test perform_validation basic functionality\"\"\"\n        # Mock all external calls to avoid actual network operations\n        mock_fqdn_resolve.return_value = (\n            [],\n            True,\n            \"DNS resolution error\",\n        )  # DNS resolution failed\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"mocked_extension\"\n        mock_servercert_get.return_value = None\n        mock_proxy_check.return_value = None\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # This should not crash and return a ValidationResult\n        result = self.validator.perform_validation(context)\n        self.assertIsInstance(result, ValidationResult)\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_003_perform_validation_dns_success(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_fqdn_resolve,\n    ):\n        \"\"\"Test successful TLS-ALPN validation with DNS\"\"\"\n        # Setup mocks\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"expected_extension\"\n        mock_servercert_get.return_value = \"mock_certificate\"\n        mock_proxy_check.return_value = None\n\n        # Mock the certificate validation method\n        with patch.object(\n            self.validator, \"_validate_certificate_extensions\", return_value=True\n        ):\n            context = ChallengeContext(\n                challenge_name=\"test\",\n                token=\"test_token\",\n                jwk_thumbprint=\"test_thumb\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n            )\n\n            result = self.validator.perform_validation(context)\n\n            self.assertTrue(result.success)\n            self.assertFalse(result.invalid)\n            self.assertIsNone(result.error_message)\n\n            # Verify function calls\n            mock_fqdn_resolve.assert_called_once_with(self.logger, \"example.com\", None)\n            mock_sha256_hash_hex.assert_called_once_with(\n                self.logger, \"test_token.test_thumb\"\n            )\n            mock_servercert_get.assert_called_once_with(\n                self.logger, \"example.com\", 443, None, \"example.com\"\n            )\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_004_perform_validation_dns_resolution_failed(self, mock_fqdn_resolve):\n        \"\"\"Test TLS-ALPN validation with DNS resolution failure\"\"\"\n        mock_fqdn_resolve.return_value = ([], True, \"DNS resolution error\")\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"invalid.example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:dns\", \"detail\": \"DNS resolution failed: DNS resolution error\"}',\n        )\n\n    @patch(\"acme_srv.helper.ip_validate\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_005_perform_validation_ip_success(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_ip_validate,\n    ):\n        \"\"\"Test successful TLS-ALPN validation with IP\"\"\"\n        # Setup mocks\n        mock_ip_validate.return_value = (\"192.168.1.1\", False)\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"expected_extension\"\n        mock_servercert_get.return_value = \"mock_certificate\"\n        mock_proxy_check.return_value = None\n\n        # Mock the certificate validation method\n        with patch.object(\n            self.validator, \"_validate_certificate_extensions\", return_value=True\n        ):\n            context = ChallengeContext(\n                challenge_name=\"test\",\n                token=\"test_token\",\n                jwk_thumbprint=\"test_thumb\",\n                authorization_type=\"ip\",\n                authorization_value=\"192.168.1.1\",\n            )\n\n            result = self.validator.perform_validation(context)\n\n            self.assertTrue(result.success)\n            self.assertFalse(result.invalid)\n\n            # Verify IP validation was called\n            mock_ip_validate.assert_called_once_with(self.logger, \"192.168.1.1\")\n\n    @patch(\"acme_srv.helper.ip_validate\")\n    def test_006_perform_validation_invalid_ip(self, mock_ip_validate):\n        \"\"\"Test TLS-ALPN validation with invalid IP\"\"\"\n        mock_ip_validate.return_value = (\"\", True)\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"ip\",\n            authorization_value=\"invalid.ip\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:malformed\", \"detail\": \"Invalid IP address: invalid.ip\"}',\n        )\n\n    def test_007_perform_validation_unsupported_authorization_type(self):\n        \"\"\"Test TLS-ALPN validation with unsupported authorization type\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"unsupported\",\n            authorization_value=\"test.example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:unsupported\", \"detail\": \"Unsupported authorization type: unsupported\"}',\n        )\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_008_perform_validation_cert_retrieval_failed(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_fqdn_resolve,\n    ):\n        \"\"\"Test TLS-ALPN validation with certificate retrieval failure\"\"\"\n        # Setup mocks\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"expected_extension\"\n        mock_servercert_get.return_value = None  # Simulate failure\n        mock_proxy_check.return_value = None\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertFalse(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:incorrectResponse\", \"detail\": \"Unable to retrieve server certificate for example.com\"}',\n        )\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_009_perform_validation_cert_validation_failed(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_fqdn_resolve,\n    ):\n        \"\"\"Test TLS-ALPN validation with certificate validation failure\"\"\"\n        # Setup mocks\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"expected_extension\"\n        mock_servercert_get.return_value = \"mock_certificate\"\n        mock_proxy_check.return_value = None\n\n        # Mock the certificate validation method to return False\n        with patch.object(\n            self.validator, \"_validate_certificate_extensions\", return_value=False\n        ):\n            context = ChallengeContext(\n                challenge_name=\"test\",\n                token=\"test_token\",\n                jwk_thumbprint=\"test_thumb\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n            )\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertEqual(\n                result.error_message,\n                '{\"status\": 403, \"type\": \"urn:ietf:params:acme:error:incorrectResponse\", \"detail\": \"Certificate extension validation failed\"}',\n            )\n\n    @patch(\"acme_srv.helper.cert_san_get\")\n    @patch(\"acme_srv.helper.fqdn_in_san_check\")\n    @patch(\"acme_srv.helper.cert_extensions_get\")\n    def test_010_validate_certificate_extensions_success(\n        self, mock_cert_extensions_get, mock_fqdn_in_san_check, mock_cert_san_get\n    ):\n        \"\"\"Test _validate_certificate_extensions with successful validation\"\"\"\n        # Setup mocks\n        mock_cert_san_get.return_value = [\"example.com\", \"www.example.com\"]\n        mock_fqdn_in_san_check.return_value = True\n        mock_cert_extensions_get.return_value = [\n            \"expected_extension\",\n            \"other_extension\",\n        ]\n\n        result = self.validator._validate_certificate_extensions(\n            cert=\"mock_cert\", extension_value=\"expected_extension\", fqdn=\"example.com\"\n        )\n\n        self.assertTrue(result)\n\n        # Verify function calls\n        mock_cert_san_get.assert_called_once_with(\n            self.logger, \"mock_cert\", recode=False\n        )\n        mock_fqdn_in_san_check.assert_called_once_with(\n            self.logger, [\"example.com\", \"www.example.com\"], \"example.com\"\n        )\n        mock_cert_extensions_get.assert_called_once_with(\n            self.logger, \"mock_cert\", recode=False\n        )\n\n    @patch(\"acme_srv.helper.cert_san_get\")\n    @patch(\"acme_srv.helper.fqdn_in_san_check\")\n    def test_011_validate_certificate_extensions_fqdn_not_in_san(\n        self, mock_fqdn_in_san_check, mock_cert_san_get\n    ):\n        \"\"\"Test _validate_certificate_extensions with FQDN not in SAN\"\"\"\n        # Setup mocks\n        mock_cert_san_get.return_value = [\"other.example.com\"]\n        mock_fqdn_in_san_check.return_value = False\n\n        result = self.validator._validate_certificate_extensions(\n            cert=\"mock_cert\", extension_value=\"expected_extension\", fqdn=\"example.com\"\n        )\n\n        self.assertFalse(result)\n\n    @patch(\"acme_srv.helper.cert_san_get\")\n    @patch(\"acme_srv.helper.fqdn_in_san_check\")\n    @patch(\"acme_srv.helper.cert_extensions_get\")\n    def test_012_validate_certificate_extensions_extension_not_found(\n        self, mock_cert_extensions_get, mock_fqdn_in_san_check, mock_cert_san_get\n    ):\n        \"\"\"Test _validate_certificate_extensions with extension not found\"\"\"\n        # Setup mocks\n        mock_cert_san_get.return_value = [\"example.com\"]\n        mock_fqdn_in_san_check.return_value = True\n        mock_cert_extensions_get.return_value = [\"other_extension\", \"wrong_extension\"]\n\n        result = self.validator._validate_certificate_extensions(\n            cert=\"mock_cert\", extension_value=\"expected_extension\", fqdn=\"example.com\"\n        )\n\n        self.assertFalse(result)\n\n    @patch(\"acme_srv.helper.cert_san_get\")\n    @patch(\"acme_srv.helper.fqdn_in_san_check\")\n    @patch(\"acme_srv.helper.cert_extensions_get\")\n    def test_013_validate_certificate_extensions_basic_functionality(\n        self, mock_cert_extensions_get, mock_fqdn_in_san_check, mock_cert_san_get\n    ):\n        \"\"\"Test _validate_certificate_extensions basic functionality\"\"\"\n        # Setup mocks to avoid actual certificate parsing\n        mock_cert_san_get.return_value = [\"example.com\"]\n        mock_fqdn_in_san_check.return_value = True\n        mock_cert_extensions_get.return_value = [\"expected_extension\"]\n\n        result = self.validator._validate_certificate_extensions(\n            cert=\"mock_cert\", extension_value=\"expected_extension\", fqdn=\"example.com\"\n        )\n\n        # Should return True when everything matches\n        self.assertTrue(result)\n\n    def test_014_validate_certificate_extensions_import_error(self):\n        \"\"\"Test _validate_certificate_extensions with import error\"\"\"\n        # Mock the import to raise ImportError for the helper functions\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.helper\" or (\n                    len(args) > 0 and \"acme_srv.helper\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator._validate_certificate_extensions(\n                cert=\"mock_cert\",\n                extension_value=\"expected_extension\",\n                fqdn=\"example.com\",\n            )\n\n            # Should return False when import fails\n            self.assertFalse(result)\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    @patch(\"acme_srv.helper.sha256_hash_hex\")\n    @patch(\"acme_srv.helper.b64_encode\")\n    @patch(\"acme_srv.helper.servercert_get\")\n    @patch(\"acme_srv.helper.proxy_check\")\n    def test_015_perform_validation_with_proxy_servers(\n        self,\n        mock_proxy_check,\n        mock_servercert_get,\n        mock_b64_encode,\n        mock_sha256_hash_hex,\n        mock_fqdn_resolve,\n    ):\n        \"\"\"Test TLS-ALPN validation with proxy servers configured\"\"\"\n        # Setup mocks\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_sha256_hash_hex.return_value = (\n            \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"\n        )\n        mock_b64_encode.return_value = \"expected_extension\"\n        mock_servercert_get.return_value = \"mock_certificate\"\n        mock_proxy_check.return_value = (\n            \"proxy.example.com:8080\"  # Return a proxy server\n        )\n\n        # Mock the certificate validation method\n        with patch.object(\n            self.validator, \"_validate_certificate_extensions\", return_value=True\n        ):\n            context = ChallengeContext(\n                challenge_name=\"test\",\n                token=\"test_token\",\n                jwk_thumbprint=\"test_thumb\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n            )\n            # Set proxy_servers to trigger the proxy_check code path (line 73)\n            context.proxy_servers = [\n                \"proxy1.example.com:8080\",\n                \"proxy2.example.com:8080\",\n            ]\n\n            result = self.validator.perform_validation(context)\n\n            self.assertTrue(result.success)\n            self.assertFalse(result.invalid)\n            self.assertIsNone(result.error_message)\n\n            # Verify that proxy_check was called with the correct parameters\n            mock_proxy_check.assert_called_once_with(\n                self.logger,\n                \"example.com\",\n                [\"proxy1.example.com:8080\", \"proxy2.example.com:8080\"],\n            )\n            # Verify that servercert_get was called with the proxy server\n            mock_servercert_get.assert_called_once_with(\n                self.logger, \"example.com\", 443, \"proxy.example.com:8080\", \"example.com\"\n            )\n\n\nclass TestEmailReplyChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for EmailReplyChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for Email Reply validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = EmailReplyChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"email-reply-00\")\n\n    def test_002_perform_validation_import_error(self):\n        \"\"\"Test perform_validation with import error\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        # Mock the import to raise ImportError\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.email_handler\" or (\n                    len(args) > 0 and \"acme_srv.email_handler\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertIn(\"Email handler not available\", result.error_message)\n            self.assertIn(\"import_error\", result.details)\n\n    @patch(\"acme_srv.email_handler.EmailHandler\")\n    def test_003_perform_validation_basic_functionality(self, mock_email_handler):\n        \"\"\"Test perform_validation basic functionality\"\"\"\n        # Setup a basic mock that doesn't crash\n        mock_handler_instance = Mock()\n        mock_handler_instance.receive.return_value = None\n        mock_email_handler.return_value.__enter__.return_value = mock_handler_instance\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"email\",\n            authorization_value=\"test@example.com\",\n        )\n\n        # This should not crash and return a ValidationResult\n        result = self.validator.perform_validation(context)\n        self.assertIsInstance(result, ValidationResult)\n\n    @patch(\"acme_srv.email_handler.EmailHandler\")\n    @patch.object(EmailReplyChallengeValidator, \"_generate_email_keyauth\")\n    @patch.object(EmailReplyChallengeValidator, \"_extract_email_keyauth\")\n    def test_004_perform_validation_success(\n        self, mock_extract, mock_generate, mock_email_handler\n    ):\n        \"\"\"Test successful email reply validation\"\"\"\n        # Setup mocks\n        mock_generate.return_value = (\"expected_keyauth\", \"rfc_token1\")\n        mock_extract.return_value = \"expected_keyauth\"\n\n        mock_handler_instance = Mock()\n        mock_handler_instance.receive.return_value = {\"body\": \"email_body_content\"}\n        mock_email_handler.return_value.__enter__.return_value = mock_handler_instance\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"email\",\n            authorization_value=\"test@example.com\",\n            keyauthorization=\"test_keyauth\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n        self.assertEqual(result.details[\"calculated_keyauth\"], \"expected_keyauth\")\n\n    @patch(\"acme_srv.email_handler.EmailHandler\")\n    @patch.object(EmailReplyChallengeValidator, \"_generate_email_keyauth\")\n    def test_005_perform_validation_no_email_received(\n        self, mock_generate, mock_email_handler\n    ):\n        \"\"\"Test validation with no email received\"\"\"\n        # Setup mocks\n        mock_generate.return_value = (\"expected_keyauth\", \"rfc_token1\")\n\n        mock_handler_instance = Mock()\n        mock_handler_instance.receive.return_value = None\n        mock_email_handler.return_value.__enter__.return_value = mock_handler_instance\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"email\",\n            authorization_value=\"test@example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertFalse(result.invalid)\n        self.assertEqual(\n            result.error_message, \"No email received or email body missing\"\n        )\n\n    @patch(\"acme_srv.email_handler.EmailHandler\")\n    @patch.object(EmailReplyChallengeValidator, \"_generate_email_keyauth\")\n    def test_006_perform_validation_email_missing_body(\n        self, mock_generate, mock_email_handler\n    ):\n        \"\"\"Test validation with email missing body\"\"\"\n        # Setup mocks\n        mock_generate.return_value = (\"expected_keyauth\", \"rfc_token1\")\n\n        mock_handler_instance = Mock()\n        mock_handler_instance.receive.return_value = {\"subject\": \"ACME challenge\"}\n        mock_email_handler.return_value.__enter__.return_value = mock_handler_instance\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"email\",\n            authorization_value=\"test@example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertFalse(result.invalid)\n        self.assertEqual(\n            result.error_message, \"No email received or email body missing\"\n        )\n\n    @patch(\"acme_srv.email_handler.EmailHandler\")\n    @patch.object(EmailReplyChallengeValidator, \"_generate_email_keyauth\")\n    @patch.object(EmailReplyChallengeValidator, \"_extract_email_keyauth\")\n    def test_007_perform_validation_keyauth_mismatch(\n        self, mock_extract, mock_generate, mock_email_handler\n    ):\n        \"\"\"Test validation with keyauth mismatch\"\"\"\n        # Setup mocks\n        mock_generate.return_value = (\"expected_keyauth\", \"rfc_token1\")\n        mock_extract.return_value = \"wrong_keyauth\"\n\n        mock_handler_instance = Mock()\n        mock_handler_instance.receive.return_value = {\"body\": \"email_body_content\"}\n        mock_email_handler.return_value.__enter__.return_value = mock_handler_instance\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"email\",\n            authorization_value=\"test@example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(result.error_message, \"Email keyauthorization mismatch\")\n        self.assertEqual(result.details[\"expected\"], \"expected_keyauth\")\n        self.assertEqual(result.details[\"received\"], \"wrong_keyauth\")\n\n    @patch(\"acme_srv.challenge_validators.email_reply_validator.convert_byte_to_string\")\n    @patch(\"acme_srv.challenge_validators.email_reply_validator.b64_url_encode\")\n    @patch(\"acme_srv.challenge_validators.email_reply_validator.sha256_hash\")\n    def test_007_generate_email_keyauth(\n        self, mock_sha256, mock_b64_encode, mock_convert\n    ):\n        \"\"\"Test _generate_email_keyauth method\"\"\"\n        mock_sha256.return_value = b\"hash_result\"\n        mock_b64_encode.return_value = b\"encoded_result\"\n        mock_convert.return_value = \"string_result\"\n\n        result, rfc_token = self.validator._generate_email_keyauth(\n            challenge_name=\"test_challenge\",\n            rfc_token2=\"token2\",\n            jwk_thumbprint=\"thumb\",\n            rfc_token1=\"token1\",\n        )\n\n        self.assertEqual(result, \"string_result\")\n        self.assertEqual(rfc_token, \"token1\")\n\n        # Verify function calls\n        mock_sha256.assert_called_once_with(self.logger, \"token1token2.thumb\")\n        mock_b64_encode.assert_called_once_with(self.logger, b\"hash_result\")\n        mock_convert.assert_called_once_with(b\"encoded_result\")\n\n    def test_008_filter_email_matching_subject(self):\n        \"\"\"Test _filter_email with matching subject\"\"\"\n        email_data = {\"subject\": \"ACME: token123\", \"body\": \"test email body\"}\n        rfc_token1 = \"token123\"\n\n        result = self.validator._filter_email(email_data, rfc_token1)\n\n        self.assertEqual(result, email_data)\n\n    def test_009_filter_email_non_matching_subject(self):\n        \"\"\"Test _filter_email with non-matching subject\"\"\"\n        email_data = {\"subject\": \"Different subject\", \"body\": \"test email body\"}\n        rfc_token1 = \"token123\"\n\n        result = self.validator._filter_email(email_data, rfc_token1)\n\n        self.assertIsNone(result)\n\n    def test_010_filter_email_missing_subject(self):\n        \"\"\"Test _filter_email with missing subject\"\"\"\n        email_data = {\"body\": \"test email body\"}\n        rfc_token1 = \"token123\"\n\n        result = self.validator._filter_email(email_data, rfc_token1)\n\n        self.assertIsNone(result)\n\n    def test_011_extract_email_keyauth_valid_format(self):\n        \"\"\"Test _extract_email_keyauth with valid format\"\"\"\n        email_body = \"\"\"\n        Some email content\n        -----BEGIN ACME RESPONSE-----\n        test_keyauth_value\n        -----END ACME RESPONSE-----\n        More content\n        \"\"\"\n\n        result = self.validator._extract_email_keyauth(email_body)\n\n        self.assertEqual(result, \"test_keyauth_value\")\n\n    def test_012_extract_email_keyauth_multiline_response(self):\n        \"\"\"Test _extract_email_keyauth with multiline response - current limitation\"\"\"\n        email_body = \"\"\"\n        Some email content\n        -----BEGIN ACME RESPONSE-----\n        test_keyauth_value\n        with multiple lines\n        -----END ACME RESPONSE-----\n        More content\n        \"\"\"\n\n        result = self.validator._extract_email_keyauth(email_body)\n\n        # Current implementation limitation: regex pattern [\\w=+/ -]+ doesn't match newlines\n        # so multiline ACME responses return None instead of the expected content\n        self.assertIsNone(result)\n\n    def test_013_extract_email_keyauth_no_match(self):\n        \"\"\"Test _extract_email_keyauth with no match\"\"\"\n        email_body = \"Some email content without ACME response\"\n\n        result = self.validator._extract_email_keyauth(email_body)\n\n        self.assertIsNone(result)\n\n    def test_014_extract_email_keyauth_empty_body(self):\n        \"\"\"Test _extract_email_keyauth with empty body\"\"\"\n        result = self.validator._extract_email_keyauth(\"\")\n\n        self.assertIsNone(result)\n\n    def test_015_extract_email_keyauth_none_body(self):\n        \"\"\"Test _extract_email_keyauth with None body\"\"\"\n        result = self.validator._extract_email_keyauth(None)\n\n        self.assertIsNone(result)\n\n\nclass TestTkauthChallengeValidator(unittest.TestCase):\n    \"\"\"Test cases for TkauthChallengeValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for TKAuth validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = TkauthChallengeValidator(self.logger)\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"tkauth-01\")\n\n    def test_002_perform_validation_success(self):\n        \"\"\"Test perform_validation success (placeholder implementation)\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n\n        result = self.validator.perform_validation(context)\n\n        # Based on the actual placeholder implementation\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertIsNone(result.error_message)\n        self.assertEqual(result.details[\"validation_type\"], \"tkauth-01\")\n        self.assertEqual(result.details[\"authorization_value\"], \"example.com\")\n\n\nclass TestSourceAddressValidator(unittest.TestCase):\n    \"\"\"Test cases for SourceAddressValidator\"\"\"\n\n    def setUp(self):\n        \"\"\"Setup for Source Address validator tests\"\"\"\n        self.logger = Mock(spec=logging.Logger)\n        self.validator = SourceAddressValidator(\n            self.logger, forward_check=True, reverse_check=True\n        )\n\n    def test_001_get_challenge_type(self):\n        \"\"\"Test get_challenge_type returns correct type\"\"\"\n        result = self.validator.get_challenge_type()\n        self.assertEqual(result, \"source-address\")\n\n    def test_002_perform_validation_import_error(self):\n        \"\"\"Test perform_validation with import error\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n\n        # Mock the import to raise ImportError\n        with patch(\n            \"builtins.__import__\", side_effect=ImportError(\"Module not found\")\n        ) as mock_import:\n\n            def selective_import_error(name, *args, **kwargs):\n                if name == \"acme_srv.helper\" or (\n                    len(args) > 0 and \"acme_srv.helper\" in str(args)\n                ):\n                    raise ImportError(\"Module not found\")\n                return mock_import.return_value\n\n            mock_import.side_effect = selective_import_error\n\n            result = self.validator.perform_validation(context)\n\n            self.assertFalse(result.success)\n            self.assertTrue(result.invalid)\n            self.assertIn(\"Required dependencies not available\", result.error_message)\n            self.assertIn(\"import_error\", result.details)\n\n    @patch(\"acme_srv.helper.ptr_resolve\")\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_003_perform_validation_basic_functionality(\n        self, mock_fqdn_resolve, mock_ptr_resolve\n    ):\n        \"\"\"Test perform_validation basic functionality\"\"\"\n        # Mock DNS resolution to prevent network calls\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\"], False, None)\n        mock_ptr_resolve.return_value = []\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n\n        # This should not crash and return a ValidationResult\n        result = self.validator.perform_validation(context)\n        self.assertIsInstance(result, ValidationResult)\n\n    def test_004_perform_validation_no_source_address(self):\n        \"\"\"Test perform_validation with no source address\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        # No source_address set\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n        self.assertEqual(\n            result.details[\"message\"], \"No source address provided, skipping validation\"\n        )\n\n    @patch.object(SourceAddressValidator, \"_perform_forward_check\")\n    @patch.object(SourceAddressValidator, \"_perform_reverse_check\")\n    def test_005_perform_validation_both_checks_success(\n        self, mock_reverse, mock_forward\n    ):\n        \"\"\"Test successful validation with both checks enabled\"\"\"\n        # Setup mocks\n        mock_forward.return_value = {\n            \"forward_check_passed\": True,\n            \"resolved_ips\": [\"192.168.1.1\"],\n        }\n        mock_reverse.return_value = {\n            \"reverse_check_passed\": True,\n            \"reverse_domains\": [\"example.com\"],\n        }\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n        context.dns_servers = []\n\n        result = self.validator.perform_validation(context)\n\n        self.assertTrue(result.success)\n        self.assertFalse(result.invalid)\n\n        # Verify method calls\n        mock_forward.assert_called_once_with(\"example.com\", \"192.168.1.1\", [])\n        mock_reverse.assert_called_once_with(\"example.com\", \"192.168.1.1\", [])\n\n    @patch.object(SourceAddressValidator, \"_perform_forward_check\")\n    def test_006_perform_validation_forward_check_failed(self, mock_forward):\n        \"\"\"Test validation with forward check failure\"\"\"\n        mock_forward.return_value = {\n            \"forward_check_passed\": False,\n            \"resolved_ips\": [\"192.168.1.100\"],\n        }\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n        context.dns_servers = []\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:unauthorized\", \"detail\": \"Forward check failed: Forward address check failed\"}',\n        )\n\n    @patch.object(SourceAddressValidator, \"_perform_forward_check\")\n    @patch.object(SourceAddressValidator, \"_perform_reverse_check\")\n    def test_007_perform_validation_reverse_check_failed(\n        self, mock_reverse, mock_forward\n    ):\n        \"\"\"Test validation with reverse check failure\"\"\"\n        mock_forward.return_value = {\n            \"forward_check_passed\": True,\n            \"resolved_ips\": [\"192.168.1.1\"],\n        }\n        mock_reverse.return_value = {\n            \"reverse_check_passed\": False,\n            \"reverse_domains\": [\"other.com\"],\n        }\n\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n        context.dns_servers = []\n\n        result = self.validator.perform_validation(context)\n\n        self.assertFalse(result.success)\n        self.assertTrue(result.invalid)\n        self.assertEqual(\n            result.error_message,\n            '{\"status\": 400, \"type\": \"urn:ietf:params:acme:error:unauthorized\", \"detail\": \"Reverse check failed: Reverse address check failed\"}',\n        )\n\n    def test_008_perform_validation_forward_only(self):\n        \"\"\"Test validation with only forward check enabled\"\"\"\n        validator = SourceAddressValidator(\n            self.logger, forward_check=True, reverse_check=False\n        )\n\n        with patch.object(validator, \"_perform_forward_check\") as mock_forward:\n            mock_forward.return_value = {\n                \"forward_check_passed\": True,\n                \"resolved_ips\": [\"192.168.1.1\"],\n            }\n\n            context = ChallengeContext(\n                challenge_name=\"test\",\n                token=\"test_token\",\n                jwk_thumbprint=\"test_thumb\",\n                authorization_type=\"dns\",\n                authorization_value=\"example.com\",\n            )\n            context.source_address = \"192.168.1.1\"\n            context.dns_servers = []\n\n            result = validator.perform_validation(context)\n\n            self.assertTrue(result.success)\n            self.assertFalse(result.invalid)\n            mock_forward.assert_called_once()\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_009_perform_forward_check_success(self, mock_fqdn_resolve):\n        \"\"\"Test _perform_forward_check success\"\"\"\n        mock_fqdn_resolve.return_value = ([\"192.168.1.1\", \"192.168.1.2\"], False, None)\n\n        result = self.validator._perform_forward_check(\"example.com\", \"192.168.1.1\", [])\n\n        self.assertTrue(result[\"forward_check_passed\"])\n        self.assertEqual(result[\"resolved_ips\"], [\"192.168.1.1\", \"192.168.1.2\"])\n        self.assertEqual(result[\"domain\"], \"example.com\")\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_010_perform_forward_check_failure(self, mock_fqdn_resolve):\n        \"\"\"Test _perform_forward_check failure\"\"\"\n        mock_fqdn_resolve.return_value = (\n            [\"192.168.1.100\"],\n            False,\n            None,\n        )  # Different IP\n\n        result = self.validator._perform_forward_check(\"example.com\", \"192.168.1.1\", [])\n\n        self.assertFalse(result[\"forward_check_passed\"])\n        self.assertEqual(result[\"resolved_ips\"], [\"192.168.1.100\"])\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_011_perform_forward_check_exception(self, mock_fqdn_resolve):\n        \"\"\"Test _perform_forward_check with exception\"\"\"\n        mock_fqdn_resolve.side_effect = Exception(\"DNS error\")\n\n        result = self.validator._perform_forward_check(\"example.com\", \"192.168.1.1\", [])\n\n        self.assertFalse(result[\"forward_check_passed\"])\n        self.assertEqual(result[\"error\"], \"DNS error\")\n\n    @patch(\"acme_srv.helper.ptr_resolve\")\n    def test_012_perform_reverse_check_success(self, mock_ptr_resolve):\n        \"\"\"Test _perform_reverse_check success\"\"\"\n        mock_ptr_resolve.return_value = [\"example.com\", \"www.example.com\"]\n\n        with patch.object(self.validator, \"_domain_matches\", return_value=True):\n            result = self.validator._perform_reverse_check(\n                \"example.com\", \"192.168.1.1\", []\n            )\n\n            self.assertTrue(result[\"reverse_check_passed\"])\n            self.assertEqual(\n                result[\"reverse_domains\"], [\"example.com\", \"www.example.com\"]\n            )\n\n    @patch(\"acme_srv.helper.ptr_resolve\")\n    def test_013_perform_reverse_check_failure(self, mock_ptr_resolve):\n        \"\"\"Test _perform_reverse_check failure\"\"\"\n        mock_ptr_resolve.return_value = [\"other.com\"]\n\n        with patch.object(self.validator, \"_domain_matches\", return_value=False):\n            result = self.validator._perform_reverse_check(\n                \"example.com\", \"192.168.1.1\", []\n            )\n\n            self.assertFalse(result[\"reverse_check_passed\"])\n\n    @patch(\"acme_srv.helper.ptr_resolve\")\n    def test_014_perform_reverse_check_exception(self, mock_ptr_resolve):\n        \"\"\"Test _perform_reverse_check with exception\"\"\"\n        mock_ptr_resolve.side_effect = Exception(\"PTR error\")\n\n        result = self.validator._perform_reverse_check(\"example.com\", \"192.168.1.1\", [])\n\n        self.assertFalse(result[\"reverse_check_passed\"])\n        self.assertEqual(result[\"error\"], \"PTR error\")\n\n    def test_015_domain_matches_exact(self):\n        \"\"\"Test _domain_matches with exact match\"\"\"\n        result = self.validator._domain_matches(\"example.com\", \"example.com\")\n        self.assertTrue(result)\n\n    def test_016_domain_matches_subdomain(self):\n        \"\"\"Test _domain_matches with subdomain\"\"\"\n        result = self.validator._domain_matches(\"example.com\", \"www.example.com\")\n        self.assertTrue(result)\n\n    def test_017_domain_matches_no_match(self):\n        \"\"\"Test _domain_matches with no match\"\"\"\n        result = self.validator._domain_matches(\"example.com\", \"other.com\")\n        self.assertFalse(result)\n\n    def test_018_domain_matches_case_insensitive(self):\n        \"\"\"Test _domain_matches is case insensitive\"\"\"\n        result = self.validator._domain_matches(\"Example.Com\", \"EXAMPLE.COM\")\n        self.assertTrue(result)\n\n    def test_019_domain_matches_trailing_dots(self):\n        \"\"\"Test _domain_matches handles trailing dots\"\"\"\n        result = self.validator._domain_matches(\"example.com.\", \"example.com\")\n        self.assertTrue(result)\n\n    def test_020_perform_validation_context_options_override(self):\n        \"\"\"Test perform_validation with context options overriding check settings\"\"\"\n        context = ChallengeContext(\n            challenge_name=\"test\",\n            token=\"test_token\",\n            jwk_thumbprint=\"test_thumb\",\n            authorization_type=\"dns\",\n            authorization_value=\"example.com\",\n        )\n        context.source_address = \"192.168.1.1\"\n        context.dns_servers = []\n        # Set options to override the validator's default settings\n        context.options = {\n            \"forward_address_check\": False,\n            \"reverse_address_check\": False,\n        }\n\n        # Mock the forward and reverse check methods to track if they're called\n        with patch.object(\n            self.validator, \"_perform_forward_check\"\n        ) as mock_forward, patch.object(\n            self.validator, \"_perform_reverse_check\"\n        ) as mock_reverse:\n\n            result = self.validator.perform_validation(context)\n\n            # Since both checks are disabled via options, neither should be called\n            mock_forward.assert_not_called()\n            mock_reverse.assert_not_called()\n\n            # Should return success since no validation is performed\n            self.assertTrue(result.success)\n            self.assertFalse(result.invalid)\n\n    @patch(\"acme_srv.helper.fqdn_resolve\")\n    def test_021_perform_forward_check_dns_error_logging(self, mock_fqdn_resolve):\n        \"\"\"Test _perform_forward_check with DNS resolution error and logging\"\"\"\n        # Setup mock to return an error message\n        mock_fqdn_resolve.return_value = ([], False, \"DNS resolution timeout\")\n\n        result = self.validator._perform_forward_check(\"example.com\", \"192.168.1.1\", [])\n\n        self.assertFalse(result[\"forward_check_passed\"])\n        self.assertEqual(result[\"error\"], \"DNS resolution timeout\")\n        self.assertEqual(result[\"domain\"], \"example.com\")\n\n        # Verify that the error was logged\n        self.logger.error.assert_called_once_with(\n            \"Forward address check DNS resolution failed: %s\", \"DNS resolution timeout\"\n        )\n\n    def test_022_domain_matches_empty_resolved_domain(self):\n        \"\"\"Test _domain_matches with empty resolved_domain returns False\"\"\"\n        # Test with None resolved_domain\n        result = self.validator._domain_matches(\"example.com\", None)\n        self.assertFalse(result)\n\n        # Test with empty string resolved_domain\n        result = self.validator._domain_matches(\"example.com\", \"\")\n        self.assertFalse(result)\n\n        # Test with whitespace-only resolved_domain\n        result = self.validator._domain_matches(\"example.com\", \"   \")\n        self.assertFalse(result)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_cli.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport importlib\nimport configparser\nimport sys\nimport datetime\nfrom unittest.mock import patch, Mock, mock_open\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._load_cfg\")\n    @patch(\"argparse.ArgumentParser\")\n    def setUp(self, mock_arg, mock_lcfg):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from tools.a2c_cli import (\n            CommandLineInterface,\n            KeyOperations,\n            MessageOperations,\n            is_url,\n            csv_dump,\n            generate_random_string,\n            file_dump,\n            file_load,\n            logger_setup,\n        )\n\n        self.a2ccli = CommandLineInterface()\n        self.keyops = KeyOperations(logger=self.logger)\n        self.msgops = MessageOperations(logger=self.logger)\n        self.is_url = is_url\n        self.csv_dump = csv_dump\n        self.file_dump = file_dump\n        self.generate_random_string = generate_random_string\n        self.file_load = file_load\n        self.logger_setup = logger_setup\n\n    def test_001_always_pass(self):\n        \"\"\"test successful tos check\"\"\"\n        self.assertTrue(\"foo\")\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_002_help_print(self, mock_cliprint):\n        \"\"\"test print help\"\"\"\n        self.a2ccli.help_print()\n        self.assertTrue(mock_cliprint.called)\n\n    def test_003_prompt_get(self):\n        \"\"\"test _prompt_get default status\"\"\"\n        self.assertEqual(\"[server missing]:\", self.a2ccli._prompt_get())\n\n    def test_004_prompt_get(self):\n        \"\"\"test _prompt_get changed status\"\"\"\n        self.a2ccli.status = \"status\"\n        self.assertEqual(\"[status]:\", self.a2ccli._prompt_get())\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_005_intro_print(self, mock_cliprint):\n        \"\"\"test print help\"\"\"\n        self.a2ccli._intro_print()\n        self.assertTrue(mock_cliprint.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_006_exec_cmd(self, mock_cliprint, mock_help_print, mock_cmdchk):\n        \"\"\"test exec_cmd correct command\"\"\"\n        cmdinput = \"/foo\"\n        self.a2ccli._exec_cmd(cmdinput)\n        self.assertFalse(mock_cliprint.called)\n        self.assertFalse(mock_help_print.called)\n        self.assertTrue(mock_cmdchk.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_007_exec_cmd(self, mock_cliprint, mock_help_print, mock_cmdchk):\n        \"\"\"test exec_cmd command without leading slash\"\"\"\n        cmdinput = \"foo\"\n        self.a2ccli._exec_cmd(cmdinput)\n        self.assertTrue(mock_cliprint.called)\n        self.assertTrue(mock_help_print.called)\n        self.assertFalse(mock_cmdchk.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_008_exec_cmd(self, mock_cliprint, mock_help_print, mock_cmdchk):\n        \"\"\"test exec_cmd command only one char\"\"\"\n        cmdinput = \"1\"\n        self.a2ccli._exec_cmd(cmdinput)\n        self.assertFalse(mock_cliprint.called)\n        self.assertFalse(mock_help_print.called)\n        self.assertFalse(mock_cmdchk.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_009_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\")\n        self.assertTrue(mock_print.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime.datetime\")\n    def test_010_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print without text\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(None)\n        self.assertFalse(mock_print.called)\n        # self.assertTrue(mock_datetime.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_011_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\", date_print=False)\n        self.assertTrue(mock_print.called)\n        mock_print.assert_called_with(\"foo\")\n        self.assertFalse(mock_datetime.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_012_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\", date_print=True)\n        self.assertTrue(mock_print.called)\n        mock_print.assert_called_with(\"datetime foo\\n\")\n        self.assertFalse(mock_datetime.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_013_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\")\n        self.assertTrue(mock_print.called)\n        mock_print.assert_called_with(\"datetime foo\\n\")\n        self.assertFalse(mock_datetime.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_014_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\", printreturn=True)\n        self.assertTrue(mock_print.called)\n        mock_print.assert_called_with(\"datetime foo\\n\")\n        self.assertFalse(mock_datetime.called)\n\n    @patch(\"builtins.print\")\n    @patch(\"tools.a2c_cli.datetime\")\n    def test_015_cli_print(self, mock_datetime, mock_print):\n        \"\"\"test _cli_print\"\"\"\n        mock_datetime.datetime.now.return_value.strftime.return_value = \"datetime\"\n        self.a2ccli._cli_print(\"foo\", printreturn=False)\n        self.assertTrue(mock_print.called)\n        mock_print.assert_called_with(\"datetime foo\")\n        self.assertFalse(mock_datetime.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    def test_016_command_check(self, mock_help_print):\n        \"\"\"test _command check with help paramter\"\"\"\n        command = \"help\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_help_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    def test_017_command_check(self, mock_help_print):\n        \"\"\"test _command check with help paramter\"\"\"\n        command = \"H\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_help_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._server_set\")\n    def test_018_command_check(self, mock_server_set):\n        \"\"\"test __servr_set()\"\"\"\n        command = \"server foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_server_set.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    def test_019_command_check(self, mock_help_print, mock_cli_print):\n        \"\"\"test _command check with unconfigured environement\"\"\"\n        command = \"/foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_cli_print.called)\n        self.assertTrue(mock_help_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface.help_print\")\n    def test_020_command_check(self, mock_help_print, mock_cli_print):\n        \"\"\"test _command check with unknown command\"\"\"\n        self.a2ccli.status = \"Configured\"\n        command = \"/foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_cli_print.called)\n        self.assertTrue(mock_help_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._key_operations\")\n    def test_021_command_check(self, mock_keyops):\n        \"\"\"test _command check with key generator\"\"\"\n        command = \"key foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_keyops.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._quit\")\n    def test_022_command_check(self, mock_quit):\n        \"\"\"test _command check with quit\"\"\"\n        command = \"quit\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_quit.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._quit\")\n    def test_023_command_check(self, mock_quit):\n        \"\"\"test _command check with key quit\"\"\"\n        command = \"Q\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_quit.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._config_operations\")\n    def test_024_command_check(self, mock_cfg):\n        \"\"\"test _command check with config\"\"\"\n        command = \"config foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._report_operations\")\n    def test_025_command_check(self, mock_report):\n        \"\"\"test _command check with _report_operations\"\"\"\n        self.a2ccli.status = \"Configured\"\n        command = \"report foo\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_report.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._report_operations\")\n    def test_026_command_check(self, mock_report, mock_cli):\n        \"\"\"test _command check with report operations but incomplete config\"\"\"\n        command = \"report foo\"\n        self.a2ccli._command_check(command)\n        self.assertFalse(mock_report.called)\n        self.assertTrue(mock_cli.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._certificate_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._message_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._report_operations\")\n    def test_027_command_check(self, mock_report, mock_message, mock_cert, mock_cli):\n        \"\"\"test _command check with report command\"\"\"\n        command = \"report foo\"\n        self.a2ccli.status = \"Configured\"\n        self.a2ccli._command_check(command)\n        self.assertTrue(mock_report.called)\n        self.assertFalse(mock_message.called)\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_cli.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._certificate_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._message_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._report_operations\")\n    def test_028_command_check(self, mock_report, mock_message, mock_cert, mock_cli):\n        \"\"\"test _command check with report command\"\"\"\n        command = \"message foo\"\n        self.a2ccli.status = \"Configured\"\n        self.a2ccli._command_check(command)\n        self.assertFalse(mock_report.called)\n        self.assertFalse(mock_cert.called)\n        self.assertTrue(mock_message.called)\n        self.assertFalse(mock_cli.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._certificate_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._message_operations\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._report_operations\")\n    def test_029_command_check(self, mock_report, mock_message, mock_cert, mock_cli):\n        \"\"\"test _command check with report command\"\"\"\n        command = \"certificate foo\"\n        self.a2ccli.status = \"Configured\"\n        self.a2ccli._command_check(command)\n        self.assertFalse(mock_report.called)\n        self.assertTrue(mock_cert.called)\n        self.assertFalse(mock_message.called)\n        self.assertFalse(mock_cli.called)\n\n    @patch(\"tools.a2c_cli.is_url\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_030_server_set(self, mock_cli_print, mock_is_url):\n        \"\"\"test _server_set all good\"\"\"\n        command = \"server foo\"\n        mock_is_url.return_value = True\n        self.a2ccli._server_set(command)\n        self.assertEqual(self.a2ccli.server, \"foo\")\n        self.assertEqual(self.a2ccli.status, \"Key missing\")\n        self.assertFalse(mock_cli_print.called)\n\n    @patch(\"tools.a2c_cli.is_url\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_031_server_set(self, mock_cli_print, mock_is_url):\n        \"\"\"test _server_set all good\"\"\"\n        command = \"server foo\"\n        mock_is_url.return_value = True\n        self.a2ccli.key = \"key\"\n        self.a2ccli._server_set(command)\n        self.assertEqual(self.a2ccli.server, \"foo\")\n        self.assertEqual(self.a2ccli.status, \"Configured\")\n        self.assertFalse(mock_cli_print.called)\n\n    @patch(\"tools.a2c_cli.is_url\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_032_server_set(self, mock_cli_print, mock_is_url):\n        \"\"\"test _server_set all wrong url specified\"\"\"\n        command = \"server foo\"\n        mock_is_url.return_value = False\n        self.a2ccli._server_set(command)\n        self.assertFalse(self.a2ccli.server)\n        self.assertEqual(self.a2ccli.status, \"server missing\")\n        self.assertTrue(mock_cli_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_033_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations generate command\"\"\"\n        command = \"key generate foo\"\n        self.a2ccli._key_operations(command)\n        self.assertTrue(mock_gen.called)\n        self.assertFalse(mock_load.called)\n        self.assertFalse(mock_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_034_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations load command\"\"\"\n        command = \"key load foo\"\n        self.a2ccli._key_operations(command)\n        self.assertFalse(mock_gen.called)\n        self.assertTrue(mock_load.called)\n        self.assertFalse(mock_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_035_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations unknown command\"\"\"\n        command = \"key bar foo\"\n        self.a2ccli._key_operations(command)\n        self.assertFalse(mock_gen.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_036_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations incomplete command\"\"\"\n        command = \"key foo\"\n        self.a2ccli._key_operations(command)\n        self.assertFalse(mock_gen.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_print.called)\n\n    @patch(\"json.dumps\")\n    @patch(\"jwcrypto.jwk.JWK.generate.export_public\")\n    @patch(\"jwcrypto.jwk.JWK.generate.export_private\")\n    @patch(\"jwcrypto.jwk.JWK.generate\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    def test_037_key_generate(\n        self, mock_fd, mock_jwk, mock_exp_priv, mock_export_public, mock_json_dump\n    ):\n        \"\"\"test key generation  all ok\"\"\"\n        self.keyops.print = Mock()\n        mock_exp_priv.return_value = {\"foo\": \"bar\"}\n        mock_export_public.return_value = {\"foo\": \"bar\"}\n        mock_json_dump.return_value = \"json_dump\"\n        self.keyops.generate(\"file_name\")\n        self.assertTrue(mock_jwk.called)\n        self.assertTrue(mock_fd.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_038_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations server missing\"\"\"\n        command = \"key foo foo\"\n        self.a2ccli._key_operations(command)\n        self.assertFalse(mock_gen.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_print.called)\n        self.assertEqual(\"server missing\", self.a2ccli.status)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.KeyOperations.generate\")\n    @patch(\"tools.a2c_cli.KeyOperations.load\")\n    def test_039_key_operations(self, mock_load, mock_gen, mock_print):\n        \"\"\"test key operations server configured\"\"\"\n        command = \"key bar foo\"\n        self.a2ccli.server = \"server\"\n        self.a2ccli._key_operations(command)\n        self.assertFalse(mock_gen.called)\n        self.assertFalse(mock_load.called)\n        self.assertTrue(mock_print.called)\n        self.assertEqual(\"Configured\", self.a2ccli.status)\n\n    @patch(\"json.dumps\")\n    @patch(\"jwcrypto.jwk.JWK.generate\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    def test_038_key_generate(self, mock_fd, mock_jwk, mock_json_dump):\n        \"\"\"test key generation  exception during filedump\"\"\"\n        mock_fd.side_effect = Exception(\"exc_fd\")\n        self.keyops.print = Mock()\n        mock_json_dump.return_value = \"json_dump\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.keyops.generate(\"file_name\")\n        self.assertIn(\n            \"ERROR:test_a2c:Key generation failed: exc_fd\",\n            lcm.output,\n        )\n        self.assertTrue(mock_jwk.called)\n        self.assertTrue(mock_fd.called)\n\n    def test_039_isurl(self):\n        \"\"\"test is_url\"\"\"\n        url = \"http://foo.bar\"\n        self.assertTrue(self.is_url(url))\n\n    def test_040_isurl(self):\n        \"\"\"test is_url\"\"\"\n        url = \"https://foo.bar\"\n        self.assertTrue(self.is_url(url))\n\n    def test_041_isurl(self):\n        \"\"\"test is_url\"\"\"\n        url = \"https://foo.bar/foo\"\n        self.assertTrue(self.is_url(url))\n\n    def test_042_isurl(self):\n        \"\"\"test is_url\"\"\"\n        url = \"https://foo.bar:80/foo\"\n        self.assertTrue(self.is_url(url))\n\n    def test_043_isurl(self):\n        \"\"\"test is_url\"\"\"\n        url = \"foo.bar\"\n        self.assertFalse(self.is_url(url))\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_044_message_operations(self, mock_sign, mock_send, mock_print):\n        \"\"\"test message operations all ok\"\"\"\n        command = \"message sign foo\"\n        self.a2ccli._message_operations(command)\n        self.assertTrue(mock_sign.called)\n        self.assertFalse(mock_send.called)\n        self.assertTrue(mock_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_045_message_operations(self, mock_sign, mock_send, mock_print):\n        \"\"\"test message operations all ok\"\"\"\n        command = \"message send foo\"\n        self.a2ccli._message_operations(command)\n        self.assertTrue(mock_sign.called)\n        self.assertTrue(mock_send.called)\n        self.assertFalse(mock_print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_046_message_operations(self, mock_sign, mock_send, mock_print):\n        \"\"\"test message operations all ok\"\"\"\n        command = \"message send\"\n        self.a2ccli._message_operations(command)\n        self.assertFalse(mock_sign.called)\n        self.assertFalse(mock_send.called)\n        self.assertTrue(mock_print.called)\n\n    @patch(\"jwcrypto.jws.JWS.serialize\")\n    @patch(\"jwcrypto.jws.JWS.add_signature\")\n    def test_047_msgops_sign(self, mock_add_sig, mock_serialize):\n        \"\"\"test add signature\"\"\"\n        key = {\"kid\": \"kid\"}\n        message = \"message\"\n        mock_serialize.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.msgops.sign(key, message))\n\n    @patch(\"requests.post\")\n    def test_048_msgops_send(self, mock_post):\n        \"\"\"test add signature\"\"\"\n        mock_post.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.msgops.send(\"server\", \"message\"))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"csv.writer\")\n    def test_049__csv_dump(self, mock_csv):\n        \"\"\"test csv dump\"\"\"\n        self.csv_dump(self.logger, \"filename\", \"content\")\n        self.assertTrue(mock_csv.called)\n\n    def test_050_helper_generate_random_string(self):\n        \"\"\"test date_to_uts_utc without format\"\"\"\n        self.assertEqual(5, len(self.generate_random_string(self.logger, 5)))\n\n    def test_051_helper_generate_random_string(self):\n        \"\"\"test date_to_uts_utc without format\"\"\"\n        self.assertEqual(15, len(self.generate_random_string(self.logger, 15)))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_052__file_dump(self):\n        \"\"\"test csv dump\"\"\"\n        self.file_dump(self.logger, \"filename\", \"content\")\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_053__file_load(self):\n        \"\"\"test csv dump\"\"\"\n        self.assertEqual(\"foo\", self.file_load(self.logger, \"filename\"))\n\n    @patch(\"time.sleep\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\\nbar\"), create=True)\n    def test_054__load_cfg(self, mock_check, mock_sleep):\n        \"\"\"test _load_cfg\"\"\"\n        self.a2ccli._load_cfg(\"filename\")\n        self.assertTrue(mock_check.called)\n        self.assertFalse(mock_sleep.called)\n\n    @patch(\"time.sleep\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"builtins.open\", mock_open(read_data=\"sleep 10\\nbar\"), create=True)\n    def test_055__load_cfg(self, mock_check, mock_sleep):\n        \"\"\"test _load_cfg with sleep command\"\"\"\n        self.a2ccli._load_cfg(\"filename\")\n        self.assertTrue(mock_check.called)\n        self.assertTrue(mock_sleep.called)\n\n    @patch(\"time.sleep\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"builtins.open\", mock_open(read_data=\"sleep\\nbar\"), create=True)\n    def test_056__load_cfg(self, mock_check, mock_sleep):\n        \"\"\"test _load_cfg with sleep command - slit failes\"\"\"\n        self.a2ccli._load_cfg(\"filename\")\n        self.assertTrue(mock_check.called)\n        self.assertTrue(mock_sleep.called)\n\n    @patch(\"time.sleep\")\n    @patch(\"tools.a2c_cli.CommandLineInterface._command_check\")\n    @patch(\"builtins.open\", mock_open(read_data=\"#foo\\n#bar\"), create=True)\n    def test_057__load_cfg(self, mock_check, mock_sleep):\n        \"\"\"test _load_cfg\"\"\"\n        self.a2ccli._load_cfg(\"filename\")\n        self.assertFalse(mock_check.called)\n        self.assertFalse(mock_sleep.called)\n\n    @patch(\"sys.exit\")\n    def test_058__quit(self, mock_exit):\n        \"\"\"test _quit()\"\"\"\n        self.a2ccli._quit()\n        self.assertTrue(mock_exit.called)\n\n    @patch(\"jwcrypto.jwk.JWK.from_json\")\n    @patch(\"tools.a2c_cli.file_load\")\n    @patch(\"os.path.exists\")\n    def test_059_keyops_load(self, mock_exists, mock_fload, mock_json):\n        \"\"\"test keyoperations.load()\"\"\"\n        self.keyops.print = Mock()\n        mock_exists.return_value = False\n        mock_json.return_value = \"key\"\n        self.assertFalse(self.keyops.load(\"filename\"))\n        self.assertFalse(mock_fload.called)\n        self.assertFalse(mock_json.called)\n        self.assertTrue(self.keyops.print.called)\n\n    @patch(\"jwcrypto.jwk.JWK.from_json\")\n    @patch(\"tools.a2c_cli.file_load\")\n    @patch(\"os.path.exists\")\n    def test_060_keyops_load(self, mock_exists, mock_fload, mock_json):\n        \"\"\"test keyoperations.load()\"\"\"\n        self.keyops.print = Mock()\n        mock_exists.return_value = True\n        mock_json.return_value = \"key\"\n        self.assertEqual(\"key\", self.keyops.load(\"filename\"))\n        self.assertTrue(mock_fload.called)\n        self.assertTrue(mock_json.called)\n        self.assertTrue(self.keyops.print.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    def test_061_config_ops(self, mock_print):\n        \"\"\"test config_operations\"\"\"\n        self.a2ccli._config_operations(\"foo\")\n        self.assertTrue(mock_print.called)\n\n    def test_062_certificate_operations(self):\n        \"\"\"test certificate operations\"\"\"\n        self.a2ccli._certificate_operations(\"foo\")\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_063_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations with incomplete command\"\"\"\n        self.a2ccli._report_operations(\"report bar\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_send.called)\n        self.assertFalse(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_064_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations with unknown format\"\"\"\n        self.a2ccli._report_operations(\"report text foo.txt\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_send.called)\n        self.assertFalse(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_065_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations without fileextension\"\"\"\n        self.a2ccli._report_operations(\"report text foo\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_send.called)\n        self.assertFalse(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_066_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations request error - no message tag in json response\"\"\"\n        mockresponse = Mock()\n        mock_send.return_value = mockresponse\n        mockresponse.status_code = 400\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.a2ccli._report_operations(\"report text foo.csv\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_send.called)\n        self.assertTrue(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_067_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations request error - message tag in json response\"\"\"\n        mockresponse = Mock()\n        mock_send.return_value = mockresponse\n        mockresponse.status_code = 400\n        mockresponse.json = lambda: {\"message\": \"mesasge\"}\n        self.a2ccli._report_operations(\"report text foo.csv\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_send.called)\n        self.assertTrue(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_068_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations request error - detail tag in json response\"\"\"\n        mockresponse = Mock()\n        mock_send.return_value = mockresponse\n        mockresponse.status_code = 400\n        mockresponse.json = lambda: {\"detail\": \"detail\"}\n        self.a2ccli._report_operations(\"report text foo.csv\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_send.called)\n        self.assertTrue(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_069_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations request success - csv dump\"\"\"\n        mockresponse = Mock()\n        mock_send.return_value = mockresponse\n        mockresponse.status_code = 200\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.a2ccli._report_operations(\"report text foo.csv\")\n        self.assertTrue(mock_print.called)\n        self.assertFalse(mock_fdump.called)\n        self.assertTrue(mock_cdump.called)\n        self.assertTrue(mock_send.called)\n        self.assertTrue(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._cli_print\")\n    @patch(\"tools.a2c_cli.file_dump\")\n    @patch(\"tools.a2c_cli.csv_dump\")\n    @patch(\"tools.a2c_cli.MessageOperations.send\")\n    @patch(\"tools.a2c_cli.MessageOperations.sign\")\n    def test_070_report_operations(\n        self, mock_sign, mock_send, mock_cdump, mock_fdump, mock_print\n    ):\n        \"\"\"test report operations request success - csv dump\"\"\"\n        mockresponse = Mock()\n        mock_send.return_value = mockresponse\n        mockresponse.status_code = 200\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.a2ccli._report_operations(\"report text foo.json\")\n        self.assertTrue(mock_print.called)\n        self.assertTrue(mock_fdump.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_send.called)\n        self.assertTrue(mock_sign.called)\n\n    @patch(\"tools.a2c_cli.CommandLineInterface._intro_print\")\n    @patch(\"builtins.input\", side_effect=[\"5\", \"6\", \"/Q\"])\n    def test_071_start(self, mock_input, mock_intro):\n        \"\"\"mock start\"\"\"\n        with self.assertRaises(SystemExit) as cm:\n            self.a2ccli.start()\n        self.assertEqual(cm.exception.code, 0)\n        self.assertRaises(SystemExit)\n\n    def test_162_logger_setup(self):\n        \"\"\"logger setup\"\"\"\n        self.assertTrue(self.logger_setup(False))\n\n    def test_163_logger_setup(self):\n        \"\"\"logger setup\"\"\"\n        self.assertTrue(self.logger_setup(True))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_cmp_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, R0913, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, mock_open, Mock\n\n# from OpenSSL import crypto\nimport shutil\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for generica cmp_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.cmp_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        pass\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_002_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load wrong cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cmd predefined in cahandler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_cmd\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"foo\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load popo predefined in cahandler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_popo\": \"pop\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": \"pop\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cmd and popo predefined in cahandler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_cmd\": \"foo\", \"cmp_popo\": \"popo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"foo\", \"popo\": \"popo\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_007_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmp_openssl_bin parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_openssl_bin\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n        self.assertEqual(\"foo\", self.cahandler.openssl_bin)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_008_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmp_recipient-dir parameter\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/foo\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_009_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmd_tmp-cmp_recipient startwith '/'\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"/foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/foo\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_010_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmd_tmp-cmp_recipient contains ,\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"fo,o\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/fo/o\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_011_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmd_tmp-cmp_recipient contains ,blank\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"fo, o\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/fo/o\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_012_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmd_tmp-cmp_recipient contains ,blank and ,\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"foo, bar,doo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/foo/bar/doo\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_013_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmd_tmp-cmp_recipient contains ,blank and ,\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_recipient\": \"foo, bar, doo,bar,doo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"popo\": 0, \"recipient\": \"/foo/bar/doo/bar/doo\"}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - any parameter string\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"foo\": \"bar\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - any parameter int\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_foo\": \"1\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"foo\": \"1\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - any parameter float\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_foo\": 0.1}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        odict = {\"cmd\": \"ir\", \"foo\": \"0.1\", \"popo\": 0}\n        self.assertEqual(odict, self.cahandler.config_dic)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmp_openssl_bin not configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"WARNING:test_a2c:cmp_openssl_bin parameter missing in configuration. Using default: /usr/bin/openssl\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - cmp_recipient not configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:cmp_recipient parameter missing in configuration.\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cmp_ref\": \"cmp_ref\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with ref variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_ref_variable\": \"cmp_ref\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"cmp_ref\", self.cahandler.ref)\n\n    @patch.dict(\"os.environ\", {\"cmp_ref\": \"user_var\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with not existing ref variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_ref_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.ref)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cmp_ref:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cmp_ref\": \"cmp_ref\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template overwrite ref variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cmp_ref_variable\": \"cmp_ref\",\n            \"cmp_ref\": \"cmp_ref_local\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"cmp_ref_local\", self.cahandler.ref)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite cmp_ref variable\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cmp_secret\": \"cmp_secret\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with secret variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_secret_variable\": \"cmp_secret\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"cmp_secret\", self.cahandler.secret)\n\n    @patch.dict(\"os.environ\", {\"cmp_secret\": \"cmp_secret\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with not existing secret variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_secret_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.secret)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cmp_secret_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cmp_secret\": \"cmp_secret\"})\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template overwrite ref variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cmp_secret_variable\": \"cmp_secret\",\n            \"cmp_secret\": \"cmp_secret_local\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"cmp_secret_local\", self.cahandler.secret)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite cmp_secret variable\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_025__config_load(self, mock_load_cfg):\n        \"\"\"config load enforce cmp_boolean True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_bool\": \"True\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\n            {\"bool\": True, \"cmd\": \"ir\", \"popo\": 0}, self.cahandler.config_dic\n        )\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.load_config\")\n    def test_026__config_load(self, mock_load_cfg):\n        \"\"\"config load enforce cmp_boolean False\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cmp_bool\": \"False\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\n            {\"bool\": False, \"cmd\": \"ir\", \"popo\": 0}, self.cahandler.config_dic\n        )\n\n    def test_027_poll(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_028_trigger(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_029_revoke(self):\n        \"\"\"test revoke\"\"\"\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Revocation is not supported.\",\n            ),\n            self.cahandler.revoke(\"cert\", \"rev_reason\", \"rev_date\"),\n        )\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._config_load\")\n    def test_030__enter__(self, mock_load):\n        \"\"\"test enter\"\"\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._config_load\")\n    def test_031__enter__(self, mock_load):\n        \"\"\"test enter\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_load.called)\n\n    @patch(\"shutil.rmtree\")\n    @patch(\"os.path.exists\")\n    def test_032_tmp_dir_delete(self, mock_exists, mock_remove):\n        \"\"\"test files_delete if file exists\"\"\"\n        mock_exists.return_value = True\n        self.cahandler._tmp_dir_delete()\n        self.assertTrue(mock_remove.called)\n\n    @patch(\"shutil.rmtree\")\n    @patch(\"os.path.exists\")\n    def test_033_tmp_dir_delete(self, mock_exists, mock_remove):\n        \"\"\"test files_delete if file exists\"\"\"\n        mock_exists.return_value = False\n        self.cahandler._tmp_dir_delete()\n        self.assertFalse(mock_remove.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_034_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if no file exists\"\"\"\n        mock_exists.return_value = False\n        self.assertEqual((None, None), self.cahandler._certs_bundle())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_035_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if no file exists\"\"\"\n        mock_exists.return_value = False\n        self.assertEqual((None, None), self.cahandler._certs_bundle())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_036_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if only capubs exists\"\"\"\n        mock_exists.side_effect = (True, False)\n        self.assertEqual((None, None), self.cahandler._certs_bundle())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_037_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if only cert exists\"\"\"\n        mock_exists.side_effect = (False, True)\n        self.assertEqual((\"foo\", \"foo\"), self.cahandler._certs_bundle())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_038_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if all exists\"\"\"\n        mock_exists.side_effect = (True, True)\n        self.assertEqual((\"foofoo\", \"foo\"), self.cahandler._certs_bundle())\n\n    @patch(\n        \"builtins.open\",\n        mock_open(read_data=\"-----BEGIN CERTIFICATE-----\\nfoo\"),\n        create=True,\n    )\n    @patch(\"os.path.isfile\")\n    def test_039_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if cert exists replace begin tag\"\"\"\n        mock_exists.side_effect = (False, True)\n        self.assertEqual(\n            (\"-----BEGIN CERTIFICATE-----\\nfoo\", \"foo\"), self.cahandler._certs_bundle()\n        )\n\n    @patch(\n        \"builtins.open\",\n        mock_open(\n            read_data=\"-----BEGIN CERTIFICATE-----\\nfoo-----END CERTIFICATE-----\\n\"\n        ),\n        create=True,\n    )\n    @patch(\"os.path.isfile\")\n    def test_040_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if cert exists replace end tag\"\"\"\n        mock_exists.side_effect = (False, True)\n        self.assertEqual(\n            (\"-----BEGIN CERTIFICATE-----\\nfoo-----END CERTIFICATE-----\\n\", \"foo\"),\n            self.cahandler._certs_bundle(),\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\\n\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_041_certs_bundle(self, mock_exists):\n        \"\"\"certs bundle if cert exists replace end tag\"\"\"\n        mock_exists.side_effect = (False, True)\n        self.assertEqual((\"foo\\n\", \"foo\"), self.cahandler._certs_bundle())\n\n    def test_042_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build()\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n            \"-total_timeout\",\n            \"10\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_043_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build() with option including in config dic\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        self.cahandler.config_dic = {\"foo1\": \"bar1\", \"foo2\": \"bar2\"}\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-foo1\",\n            \"bar1\",\n            \"-foo2\",\n            \"bar2\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n            \"-total_timeout\",\n            \"10\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_044_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build() - customized msg_timeout\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        self.cahandler.config_dic = {\"msg_timeout\": 10}\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-msg_timeout\",\n            \"10\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-total_timeout\",\n            \"10\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_045_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build()\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.config_dic = {\"total_timeout\": 20}\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-total_timeout\",\n            \"20\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_046_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build() with secret\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.secret = \"secret\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        self.cahandler.config_dic = {\"total_timeout\": 20}\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-total_timeout\",\n            \"20\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_047_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build() with ref\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.ref = \"ref\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        self.cahandler.config_dic = {\"total_timeout\": 20}\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-total_timeout\",\n            \"20\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_048_opensslcmd_build(self):\n        \"\"\"test _openssl_cmd_build() with ref and secret\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        self.cahandler.ref = \"ref\"\n        self.cahandler.secret = \"secret\"\n        self.cahandler.tmp_dir = \"/tmp\"\n        self.cahandler.ca_pubs_file = \"/tmp/capubs.pem\"\n        self.cahandler.cert_file = \"/tmp/cert.pem\"\n        self.cahandler.config_dic = {\"total_timeout\": 20}\n        result = [\n            \"openssl_bin\",\n            \"cmp\",\n            \"-total_timeout\",\n            \"20\",\n            \"-csr\",\n            \"/tmp/csr.pem\",\n            \"-extracertsout\",\n            \"/tmp/capubs.pem\",\n            \"-certout\",\n            \"/tmp/cert.pem\",\n            \"-msg_timeout\",\n            \"5\",\n            \"-ref\",\n            \"ref\",\n            \"-secret\",\n            \"secret\",\n        ]\n        self.assertEqual(result, self.cahandler._opensslcmd_build())\n\n    def test_049_enroll(self):\n        \"\"\"test enroll without openssl_bin\"\"\"\n        self.assertEqual(\n            (\"Config incomplete\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._certs_bundle\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._tmp_dir_delete\")\n    @patch(\"os.path.isfile\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._opensslcmd_build\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._file_save\")\n    def test_050_enroll(\n        self,\n        mock_save,\n        mock_build,\n        mock_call,\n        mock_exists,\n        mock_del,\n        mock_bundle,\n    ):\n        \"\"\"test enroll subprocess.call returns 0\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        mock_save.return_value = True\n        mock_build.return_value = \"opensslcmd\"\n        mock_call.return_value = 0\n        mock_exists.return_value = True\n        mock_bundle.return_value = (\"cert_bundle\", \"cert_raw\")\n        mock_del.return_value = True\n        self.assertEqual(\n            (None, \"cert_bundle\", \"cert_raw\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_save.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_call.called)\n        self.assertTrue(mock_exists.called)\n        self.assertTrue(mock_del.called)\n        self.assertTrue(mock_bundle.called)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._certs_bundle\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._tmp_dir_delete\")\n    @patch(\"os.path.isfile\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._opensslcmd_build\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._file_save\")\n    def test_051_enroll(\n        self, mock_save, mock_build, mock_call, mock_exists, mock_del, mock_bundle\n    ):\n        \"\"\"test enroll subprocess.call returns other than 0\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        mock_save.return_value = True\n        mock_build.return_value = \"opensslcmd\"\n        mock_call.return_value = 25\n        mock_exists.return_value = True\n        mock_bundle.return_value = (\"cert_bundle\", \"cert_raw\")\n        mock_del.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"rc from enrollment not 0\", \"cert_bundle\", \"cert_raw\", None),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment failed with rcode: 25\", lcm.output)\n        self.assertTrue(mock_save.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_call.called)\n        self.assertTrue(mock_exists.called)\n        self.assertTrue(mock_del.called)\n        self.assertTrue(mock_bundle.called)\n\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._certs_bundle\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._tmp_dir_delete\")\n    @patch(\"os.path.isfile\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._opensslcmd_build\")\n    @patch(\"examples.ca_handler.cmp_ca_handler.CAhandler._file_save\")\n    def test_052_enroll(\n        self, mock_save, mock_build, mock_call, mock_exists, mock_del, mock_bundle\n    ):\n        \"\"\"test enroll tmp_dir does not exists\"\"\"\n        self.cahandler.openssl_bin = \"openssl_bin\"\n        mock_save.return_value = True\n        mock_build.return_value = \"opensslcmd\"\n        mock_call.return_value = 25\n        mock_exists.return_value = False\n        mock_bundle.return_value = (\"cert_bundle\", \"cert_raw\")\n        mock_del.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Enrollment failed\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment failed with rcode: 25\", lcm.output)\n        self.assertTrue(mock_save.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_call.called)\n        self.assertTrue(mock_exists.called)\n        self.assertTrue(mock_del.called)\n        self.assertFalse(mock_bundle.called)\n\n    @patch(\"builtins.open\")\n    def test_053__file_save(self, mock_op):\n        \"\"\"test file save\"\"\"\n        self.assertFalse(self.cahandler._file_save(\"filename\", \"content\"))\n        self.assertTrue(mock_op.called)\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_digicert.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openxpki_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, Mock, MagicMock\nimport requests\nimport base64\nfrom OpenSSL import crypto\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n        from examples.ca_handler.digicert_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_load\")\n    def test_003__enter__(self, mock_cfg):\n        \"\"\"test enter api hosts defined\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    def test_004_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_005_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch.object(requests, \"post\")\n    def test_006__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_post(\"url\", \"data\")\n        )\n\n    @patch(\"requests.post\")\n    def test_007__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_post(\"url\", \"data\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_008__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.text = None\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_009__api_post(self, mock_req):\n        \"\"\"test _api_post(=\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_post\"), self.cahandler._api_post(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_post\", lcm.output\n        )\n\n    @patch.object(requests, \"get\")\n    def test_010__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_get(\"url\")\n        )\n\n    @patch(\"requests.get\")\n    def test_011__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_get(\"url\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    def test_012__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((500, \"exc_api_get\"), self.cahandler._api_get(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_get\", lcm.output\n        )\n\n    @patch.object(requests, \"put\")\n    def test_013__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_put(\"url\", \"data\")\n        )\n\n    @patch(\"requests.put\")\n    def test_014__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_put(\"url\", \"data\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.put\")\n    def test_015__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.text = None\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_put(\"url\", \"data\"))\n\n    @patch(\"requests.put\")\n    def test_016__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_put\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_put\"), self.cahandler._api_put(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_put\", lcm.output\n        )\n\n    def test_017__config_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.assertEqual(\n            \"api_key parameter is missing in config file\",\n            self.cahandler._config_check(),\n        )\n\n    def test_018__config_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.cahandler.api_key = \"api_key\"\n        self.assertEqual(\n            \"organization_name parameter is missing in config file\",\n            self.cahandler._config_check(),\n        )\n\n    def test_019__config_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertFalse(self.cahandler._config_check())\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['CAhandler'] = {'foo': 'bar'}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_url\": \"api_url\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\"api_url\", self.cahandler.api_url)\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_key\": \"api_key\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertEqual(\"api_key\", self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"signature_hash\": \"signature_hash\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"signature_hash\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_type\": \"cert_type\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"cert_type\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_025_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"order_validity\": \"2\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(2, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_026_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"order_validity\": \"aa\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load order_validity:invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_027_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 20}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_028_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"30\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(30, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_029_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load request_timeout:invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_030_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"organization_name\": \"organization_name\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"organization_name\", self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_031_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"organization_id\": \"organization_id\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"organization_id\", self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_032_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"foo\", \"bar\"]'}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_033_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"foo\"]'}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.load_config\")\n    def test_034_config_load(self, mock_load, mock_eab, mock_hdl):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": \"foo\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = True, \"eab\"\n        mock_hdl.return_value = \"hdl\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_load.called)\n        self.assertEqual(\n            \"https://www.digicert.com/services/v2/\", self.cahandler.api_url\n        )\n        self.assertFalse(self.cahandler.api_key)\n        self.assertEqual(\"ssl_basic\", self.cahandler.cert_type)\n        self.assertEqual(\"sha256\", self.cahandler.signature_hash)\n        self.assertEqual(1, self.cahandler.order_validity)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertFalse(self.cahandler.organization_id)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(\"eab\", self.cahandler.eab_handler)\n        self.assertEqual(\"hdl\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_035_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.organization_id = \"organization_id\"\n        self.assertEqual((\"code\", \"content\"), self.cahandler._order_send(\"csr\", \"cn\"))\n        self.assertFalse(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_036_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.assertEqual(\n            (500, \"organisation_id is missing\"), self.cahandler._order_send(\"csr\", \"cn\")\n        )\n        self.assertFalse(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_037_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.eab_profiling = True\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.organization_name = \"organization_name\"\n        self.cahandler.organization_id = \"organization_id\"\n        self.assertEqual((\"code\", \"content\"), self.cahandler._order_send(\"csr\", \"cn\"))\n        self.assertTrue(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_038_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.eab_profiling = True\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.organization_name = \"organization_name\"\n        self.cahandler.organization_id = None\n        mock_orgid.return_value = 1\n        self.assertEqual((\"code\", \"content\"), self.cahandler._order_send(\"csr\", \"cn\"))\n        self.assertTrue(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_039_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.organization_name = \"organization_name\"\n        self.cahandler.organization_id = None\n        mock_orgid.return_value = 1\n        self.assertEqual((\"code\", \"content\"), self.cahandler._order_send(\"csr\", \"cn\"))\n        self.assertTrue(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_040_order_send(self, mock_post, mock_orgid):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.api_key = \"api_key\"\n        self.cahandler.organization_name = None\n        self.cahandler.organization_id = None\n        mock_orgid.return_value = 1\n        self.assertEqual(\n            (500, \"organisation_id is missing\"), self.cahandler._order_send(\"csr\", \"cn\")\n        )\n        self.assertFalse(mock_orgid.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_041_order_send(self, mock_post, mock_orgid, mock_ecl):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.api_key = None\n        self.cahandler.organization_name = \"organization_name\"\n        self.cahandler.organization_id = None\n        mock_orgid.return_value = 1\n        self.assertEqual(\n            (500, \"organisation_id is missing\"), self.cahandler._order_send(\"csr\", \"cn\")\n        )\n        self.assertFalse(mock_orgid.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._organiation_id_get\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_post\")\n    def test_042_order_send(self, mock_post, mock_orgid, mock_ecl):\n        \"\"\"test _order_send()\"\"\"\n        mock_post.return_value = (\"code\", \"content\")\n        self.cahandler.api_key = None\n        self.cahandler.organization_name = \"organization_name\"\n        self.cahandler.organization_id = None\n        self.cahandler.enrollment_config_log = True\n        mock_orgid.return_value = 1\n        self.assertEqual(\n            (500, \"organisation_id is missing\"), self.cahandler._order_send(\"csr\", \"cn\")\n        )\n        self.assertFalse(mock_orgid.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.b64_encode\")\n    def test_043_order_response_parse(self, mock_b64, mock_pem2der):\n        \"\"\"test _order_parse()\"\"\"\n        content_dic = {\n            \"id\": \"id\",\n            \"certificate_chain\": [{\"pem\": \"pem1\"}, {\"pem\": \"pem2\"}, {\"pem\": \"pem3\"}],\n        }\n        mock_b64.return_value = \"b64\"\n        self.assertEqual(\n            (\"pem1\\npem2\\npem3\\n\", \"b64\", \"id\"),\n            self.cahandler._order_response_parse(content_dic),\n        )\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.b64_encode\")\n    def test_044_order_response_parse(self, mock_b64, mock_pem2der):\n        \"\"\"test _order_parse()\"\"\"\n        content_dic = {\n            \"id\": \"id\",\n            \"cert_chain\": [{\"pem\": \"pem1\"}, {\"pem\": \"pem2\"}, {\"pem\": \"pem3\"}],\n        }\n        mock_b64.return_value = \"b64\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None), self.cahandler._order_response_parse(content_dic)\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Order response parsing failed: no certificate_chain in response\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.b64_encode\")\n    def test_045_order_response_parse(self, mock_b64, mock_pem2der):\n        \"\"\"test _order_parse()\"\"\"\n        content_dic = {\n            \"id\": \"id\",\n            \"certificate_chain\": [{\"pem\": \"pem1\"}, {\"_pem\": \"pem2\"}, {\"pem\": \"pem3\"}],\n        }\n        mock_b64.return_value = \"b64\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"pem1\\npem3\\n\", \"b64\", \"id\"),\n                self.cahandler._order_response_parse(content_dic),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Order response parsing failed: no pem in certificate_chain\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.b64_encode\")\n    def test_046_order_response_parse(self, mock_b64, mock_pem2der):\n        \"\"\"test _order_parse()\"\"\"\n        content_dic = {\n            \"_id\": \"id\",\n            \"certificate_chain\": [{\"pem\": \"pem1\"}, {\"pem\": \"pem2\"}, {\"pem\": \"pem3\"}],\n        }\n        mock_b64.return_value = \"b64\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"pem1\\npem2\\npem3\\n\", \"b64\", None),\n                self.cahandler._order_response_parse(content_dic),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Polling_identifier generation failed: no id in response\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_get\")\n    def test_047_organiation_id_get(self, mock_get):\n        \"\"\"test _organiation_id_get()\"\"\"\n        mock_get.return_value = (500, {\"id\": \"id\"})\n        self.cahandler.organization_name = \"organization_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._organiation_id_get()\n        self.assertIn(\"ERROR:test_a2c:Could not get organization id.\", lcm.output)\n        self.assertFalse(self.cahandler.organization_id)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_get\")\n    def test_048_organiation_id_get(self, mock_get):\n        \"\"\"test _organiation_id_get()\"\"\"\n        mock_get.return_value = (\n            200,\n            {\n                \"organizations\": [\n                    {\"name\": \"name1\", \"id\": \"id1\"},\n                    {\"name\": \"name2\", \"id\": \"id2\"},\n                    {\"name\": \"name3\", \"id\": \"id3\"},\n                ]\n            },\n        )\n        self.cahandler.organization_name = \"name1\"\n        self.assertEqual(\"id1\", self.cahandler._organiation_id_get())\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_get\")\n    def test_049_organiation_id_get(self, mock_get):\n        \"\"\"test _organiation_id_get()\"\"\"\n        mock_get.return_value = (\n            200,\n            {\n                \"organizations\": [\n                    {\"name\": \"name1\", \"id\": \"id1\"},\n                    {\"name\": \"name2\", \"id\": \"id2\"},\n                    {\"name\": \"name3\", \"id\": \"id3\"},\n                ]\n            },\n        )\n        self.cahandler.organization_name = \"name2\"\n        self.assertEqual(\"id2\", self.cahandler._organiation_id_get())\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.eab_profile_header_info_check\")\n    def test_050_csr_check(self, mock_ehichk):\n        \"\"\"test _csr_check()\"\"\"\n        mock_ehichk.return_value = \"mock_hichk\"\n        self.assertEqual(\"mock_hichk\", self.cahandler._csr_check(\"csr\"))\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.eab_profile_header_info_check\")\n    def test_051_csr_check(self, mock_ehichk):\n        \"\"\"test _csr_check()\"\"\"\n        mock_ehichk.return_value = False\n        self.assertFalse(self.cahandler._csr_check(\"csr\"))\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_response_parse\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_send\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    def test_052_enroll(\n        self, mock_cfgchk, mock_csrchk, mock_cnget, mock_ordersend, mock_orderparse\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_cfgchk.return_value = \"mock_cfgchk\"\n        mock_csrchk.return_value = \"mock_csrchk\"\n        mock_cnget.return_value = \"cn\"\n        mock_ordersend.return_value = (\"code\", \"content\")\n        mock_orderparse.return_value = (\"pem\", \"b64\", \"id\")\n        self.assertEqual(\n            (\"mock_cfgchk\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertFalse(mock_csrchk.called)\n        self.assertFalse(mock_cnget.called)\n        self.assertFalse(mock_ordersend.called)\n        self.assertFalse(mock_orderparse.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_response_parse\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_send\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    def test_053_enroll(\n        self, mock_cfgchk, mock_csrchk, mock_cnget, mock_ordersend, mock_orderparse\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = \"mock_csrchk\"\n        mock_cnget.return_value = \"cn\"\n        mock_ordersend.return_value = (\"code\", \"content\")\n        mock_orderparse.return_value = (\"pem\", \"b64\", \"id\")\n        self.assertEqual(\n            (\"mock_csrchk\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertFalse(mock_cnget.called)\n        self.assertFalse(mock_ordersend.called)\n        self.assertFalse(mock_orderparse.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_response_parse\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_send\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    def test_054_enroll(\n        self, mock_cfgchk, mock_csrchk, mock_cnget, mock_ordersend, mock_orderparse\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = False\n        mock_cnget.return_value = \"cn\"\n        mock_ordersend.return_value = (\"code\", \"content\")\n        mock_orderparse.return_value = (\"pem\", \"b64\", \"id\")\n        self.assertEqual(\n            (\"Error during order creation: code - content\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertTrue(mock_cnget.called)\n        self.assertTrue(mock_ordersend.called)\n        self.assertFalse(mock_orderparse.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_response_parse\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_send\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    def test_055_enroll(\n        self, mock_cfgchk, mock_csrchk, mock_cnget, mock_ordersend, mock_orderparse\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = False\n        mock_cnget.return_value = \"cn\"\n        mock_ordersend.return_value = (\n            \"code\",\n            {\"errors\": [{\"code\": \"code\", \"message\": \"content\"}]},\n        )\n        mock_orderparse.return_value = (\"pem\", \"b64\", \"id\")\n        self.assertEqual(\n            (\n                \"Error during order creation: code - [{'code': 'code', 'message': 'content'}]\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertTrue(mock_cnget.called)\n        self.assertTrue(mock_ordersend.called)\n        self.assertFalse(mock_orderparse.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_response_parse\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._order_send\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    def test_056_enroll(\n        self, mock_cfgchk, mock_csrchk, mock_cnget, mock_ordersend, mock_orderparse\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = False\n        mock_cnget.return_value = \"cn\"\n        mock_ordersend.return_value = (200, \"content\")\n        mock_orderparse.return_value = (\"pem\", \"b64\", \"id\")\n        self.assertEqual((False, \"pem\", \"b64\", \"id\"), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertTrue(mock_cnget.called)\n        self.assertTrue(mock_ordersend.called)\n        self.assertTrue(mock_orderparse.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_serial_get\")\n    def test_057_revoke(self, mock_serial, mock_put, mock_cfgchk):\n        \"\"\"test revoke()\"\"\"\n        mock_serial.return_value = \"serial\"\n        mock_put.return_value = (\"code\", \"content\")\n        self.assertEqual((\"code\", None, \"content\"), self.cahandler.revoke(\"cert\"))\n        self.assertFalse(mock_cfgchk.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_serial_get\")\n    def test_058_revoke(self, mock_serial, mock_put, mock_cfgchk):\n        \"\"\"test revoke()\"\"\"\n        mock_serial.return_value = \"serial\"\n        mock_put.return_value = (\"code\", \"content\")\n        self.cahandler.eab_profiling = True\n        self.assertEqual((\"code\", None, \"content\"), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_cfgchk.called)\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_serial_get\")\n    def test_059_revoke(self, mock_serial, mock_put):\n        \"\"\"test revoke()\"\"\"\n        mock_serial.return_value = None\n        mock_put.return_value = (\"code\", \"content\")\n        self.assertEqual(\n            (500, None, \"Failed to parse certificate serial\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.digicert_ca_handler.cert_serial_get\")\n    def test_060_revoke(self, mock_serial, mock_put):\n        \"\"\"test revoke()\"\"\"\n        mock_serial.return_value = \"serial\"\n        mock_put.return_value = (204, \"content\")\n        self.assertEqual((200, None, \"content\"), self.cahandler.revoke(\"cert\"))\n\n    @patch(\"examples.ca_handler.digicert_ca_handler.handler_config_check\")\n    def test_061_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_directory.py",
    "content": "import unittest\nfrom unittest.mock import MagicMock, patch, ANY\nimport os\nimport sys\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nmock_db_handler = MagicMock()\nmock_dbstore_class = MagicMock()\nmock_db_handler.DBstore = mock_dbstore_class\nsys.modules[\"acme_srv.db_handler\"] = mock_db_handler\nfrom acme_srv.directory import Directory, DirectoryConfig, DirectoryRepository\n\n\nclass TestDirectory(unittest.TestCase):\n    def setUp(self):\n        \"\"\"Set up module-level mocks before any tests run\"\"\"\n        self.mock_logger = MagicMock()\n        self.mock_dbstore = MagicMock()\n        self.mock_repository = DirectoryRepository(self.mock_dbstore, self.mock_logger)\n        self.mock_config = DirectoryConfig()\n        self.mock_cahandler = MagicMock()\n        self.mock_cahandler_instance = MagicMock()\n        self.mock_cahandler.return_value = self.mock_cahandler_instance\n        self.mock_cahandler_instance.__enter__.return_value = (\n            self.mock_cahandler_instance\n        )\n        self.mock_cahandler_instance.__exit__.return_value = None\n        self.mock_cahandler_instance.handler_check.return_value = None\n        self.directory = Directory(\n            debug=None, srv_name=\"http://localhost\", logger=self.mock_logger\n        )\n        self.directory.dbstore = self.mock_dbstore\n        self.directory.repository = self.mock_repository\n        self.directory.config = self.mock_config\n        self.directory.cahandler = self.mock_cahandler\n\n    def test_001_context_manager(self):\n        with patch.object(self.directory, \"_load_configuration\") as mock_load_config:\n            with self.directory as d:\n                mock_load_config.assert_called_once()\n                self.assertIs(d, self.directory)\n\n    def test_002_load_configuration(self):\n        # Mock config_dic to behave like configparser.ConfigParser\n        config_dic = MagicMock()\n        config_dic.getboolean.return_value = False\n        with patch(\"acme_srv.directory.load_config\", return_value=config_dic):\n            with patch(\n                \"acme_srv.directory.config_async_mode_load\", return_value=False\n            ) as mock_async_mode_load:\n                with patch.object(\n                    self.directory, \"_parse_directory_section\"\n                ) as mock_parse_dir, patch.object(\n                    self.directory, \"_parse_booleans\"\n                ) as mock_parse_bool, patch.object(\n                    self.directory, \"_parse_eab_and_profiles\"\n                ) as mock_parse_eab, patch.object(\n                    self.directory, \"_load_ca_handler\"\n                ) as mock_load_ca, patch.object(\n                    self.directory, \"_parse_cahandler_section\"\n                ) as mock_parse_cahandler_section:\n                    self.directory._load_configuration()\n                    mock_parse_dir.assert_called()\n                    mock_parse_bool.assert_called()\n                    mock_parse_eab.assert_called()\n                    mock_load_ca.assert_called()\n                    mock_parse_cahandler_section.assert_called()\n                    mock_async_mode_load.assert_called()\n\n    def test_003_parse_directory_empty(self):\n        # Mock config_dic to behave like configparser.ConfigParser\n        mock_config = MagicMock()\n        mock_config.get.side_effect = lambda section, key, fallback=None: {\n            (\"CAhandler\", \"foo\"): \"bar\"\n        }.get((section, key), fallback)\n        self.directory._parse_directory_section(mock_config)\n        self.assertFalse(self.directory.config.tos_url)\n        self.assertEqual(self.directory.config.url_prefix, \"\")\n        self.assertEqual(\n            self.directory.config.home, \"https://github.com/grindsa/acme2certifier\"\n        )\n\n    def test_003_parse_directory_section_sets_config(self):\n        # Mock config_dic to behave like configparser.ConfigParser\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"Directory\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"tos_url\": \"tos\", \"url_prefix\": \"/prefix\", \"home\": \"custom_home\"}\n            if k == \"Directory\"\n            else None\n        )\n        config_dic.get.side_effect = lambda section, key, fallback=None: None\n        self.directory._parse_directory_section(config_dic)\n        self.assertEqual(self.directory.config.tos_url, \"tos\")\n        self.assertEqual(self.directory.config.url_prefix, \"/prefix\")\n        self.assertEqual(self.directory.config.home, \"custom_home\")\n\n    def test_004_parse_caaidentities_json(self):\n        value = '[\"id1\", \"id2\"]'\n        result = self.directory._parse_caaidentities(value)\n        self.assertEqual(result, [\"id1\", \"id2\"])\n\n    def test_005_parse_caaidentities_fallback(self):\n        value = \"id1\"\n        result = self.directory._parse_caaidentities(value)\n        self.assertEqual(result, [\"id1\"])\n\n    def test_006_parse_caaidentities_error(self):\n        value = \"[invalid_json]\"\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            result = self.directory._parse_caaidentities(value)\n            self.assertEqual(result, [])\n            mock_error.assert_called()\n\n    def test_007_parse_booleans(self):\n        config_dic = MagicMock()\n        config_dic.getboolean.side_effect = lambda section, key, fallback: True\n        self.directory._parse_booleans(config_dic)\n        self.assertTrue(self.directory.config.supress_version)\n        self.assertTrue(self.directory.config.db_check)\n        self.assertTrue(self.directory.config.suppress_product_information)\n\n    def test_008_parse_booleans_error(self):\n        config_dic = MagicMock()\n        config_dic.getboolean.side_effect = Exception(\"fail\")\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            self.directory._parse_booleans(config_dic)\n            self.assertTrue(mock_error.called)\n\n    def test_009_parse_eab_and_profiles(self):\n        config_dic = {\"EABhandler\": {\"eab_handler_file\": \"file\"}}\n        with patch(\n            \"acme_srv.directory.config_profile_load\", return_value={\"profile\": \"data\"}\n        ):\n            self.directory._parse_eab_and_profiles(config_dic)\n            self.assertTrue(self.directory.config.eab)\n            self.assertEqual(self.directory.config.profiles, {\"profile\": \"data\"})\n\n    def test_010_load_ca_handler_success(self):\n        config_dic = {}\n        ca_handler_module = MagicMock()\n        ca_handler_module.CAhandler = MagicMock()\n        with patch(\n            \"acme_srv.directory.ca_handler_load\", return_value=ca_handler_module\n        ):\n            self.directory._load_ca_handler(config_dic)\n            self.assertEqual(self.directory.cahandler, ca_handler_module.CAhandler)\n\n    def test_011_load_ca_handler_failure(self):\n        config_dic = {}\n        with patch(\"acme_srv.directory.ca_handler_load\", return_value=None):\n            with patch.object(self.mock_logger, \"critical\") as mock_critical:\n                self.directory._load_ca_handler(config_dic)\n                mock_critical.assert_called()\n\n    def test_012_build_meta_information(self):\n        self.directory.config.suppress_product_information = False\n        self.directory.config.supress_version = False\n        self.directory.config.tos_url = \"tos\"\n        self.directory.config.caaidentities = [\"id1\"]\n        self.directory.config.profiles = {\"profile\": \"data\"}\n        self.directory.config.eab = True\n        meta = self.directory._build_meta_information()\n        self.assertIn(\"home\", meta)\n        self.assertIn(\"author\", meta)\n        self.assertIn(\"name\", meta)\n        self.assertIn(\"version\", meta)\n        self.assertIn(\"termsOfService\", meta)\n        self.assertIn(\"caaIdentities\", meta)\n        self.assertIn(\"profiles\", meta)\n        self.assertIn(\"externalAccountRequired\", meta)\n\n    def test_013_build_meta_information_suppress(self):\n        self.directory.config.suppress_product_information = True\n        self.directory.config.home = \"custom_home\"\n        meta = self.directory._build_meta_information()\n        self.assertIn(\"home\", meta)\n        self.assertNotIn(\"author\", meta)\n        self.assertNotIn(\"name\", meta)\n        self.assertNotIn(\"version\", meta)\n\n    def test_014_build_directory_response_db_check_ok(self):\n        self.directory.config.db_check = True\n        self.directory.dbversion = \"1.0\"\n        self.directory.repository = self.mock_repository\n        with patch.object(\n            self.mock_repository, \"get_db_version\", return_value=(\"1.0\", \"script\")\n        ):\n            resp = self.directory._build_directory_response()\n            self.assertEqual(resp[\"meta\"][\"db_check\"], \"OK\")\n\n    def test_015_build_directory_response_db_check_nok(self):\n        self.directory.config.db_check = True\n        self.directory.dbversion = \"1.0\"\n        self.directory.repository = self.mock_repository\n        with patch.object(\n            self.mock_repository, \"get_db_version\", return_value=(\"2.0\", \"script\")\n        ):\n            with patch.object(self.mock_logger, \"error\") as mock_error:\n                resp = self.directory._build_directory_response()\n                self.assertEqual(resp[\"meta\"][\"db_check\"], \"NOK\")\n                mock_error.assert_called()\n\n    def test_016_build_directory_response_db_exception(self):\n        self.directory.config.db_check = True\n        self.directory.dbversion = \"1.0\"\n        self.directory.repository = self.mock_repository\n        with patch.object(\n            self.mock_repository, \"get_db_version\", return_value=(None, None)\n        ):\n            with patch.object(self.mock_logger, \"error\") as mock_error:\n                resp = self.directory._build_directory_response()\n                self.assertEqual(resp[\"meta\"][\"db_check\"], \"NOK\")\n                mock_error.assert_called()\n\n    def test_017_build_directory_response_random_key(self):\n        resp = self.directory._build_directory_response()\n        found_random = any(\n            v\n            == \"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417\"\n            for v in resp.values()\n        )\n        self.assertTrue(found_random)\n\n    def test_018_get_directory_response_success(self):\n        self.directory.cahandler = self.mock_cahandler\n        self.mock_cahandler_instance.handler_check.return_value = None\n        resp = self.directory.get_directory_response()\n        self.assertIn(\"newAuthz\", resp)\n        self.assertIn(\"meta\", resp)\n\n    def test_019_get_directory_response_error(self):\n        self.directory.cahandler = self.mock_cahandler\n        self.mock_cahandler_instance.handler_check.return_value = \"error\"\n        resp = self.directory.get_directory_response()\n        self.assertIn(\"error\", resp)\n\n    def test_020_get_directory_response_no_handler(self):\n        self.directory.cahandler = None\n        resp = self.directory.get_directory_response()\n        self.assertIn(\"error\", resp)\n\n    def test_021_directory_get(self):\n        with patch.object(\n            self.directory, \"get_directory_response\", return_value={\"key\": \"value\"}\n        ):\n            resp = self.directory.directory_get()\n            self.assertEqual(resp, {\"key\": \"value\"})\n\n    def test_022_servername_get(self):\n        self.directory.server_name = \"test_server\"\n        self.assertEqual(self.directory.servername_get(), \"test_server\")\n\n    def test_023_parse_directory_section_calls_parse_caaidentities(self):\n        # Mock config_dic to behave like configparser.ConfigParser\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"Directory\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"tos_url\": \"tos\", \"url_prefix\": \"/prefix\", \"home\": \"custom_home\"}\n            if k == \"Directory\"\n            else None\n        )\n        # Return a non-None value for caaidentities\n        config_dic.get.side_effect = (\n            lambda section, key, fallback=None: '[\"id1\", \"id2\"]'\n            if key == \"caaidentities\"\n            else None\n        )\n        with patch.object(\n            self.directory,\n            \"_parse_caaidentities\",\n            wraps=self.directory._parse_caaidentities,\n        ) as mock_parse_caaidentities:\n            self.directory._parse_directory_section(config_dic)\n            mock_parse_caaidentities.assert_called_once_with('[\"id1\", \"id2\"]')\n\n    def test_024_repository_get_db_version_success(self):\n        mock_dbstore = MagicMock()\n        mock_dbstore.dbversion_get.return_value = (\"1.0\", \"script\")\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        result = repo.get_db_version()\n        self.assertEqual(result, (\"1.0\", \"script\"))\n\n    def test_025_repository_get_db_version_exception(self):\n        mock_dbstore = MagicMock()\n        mock_dbstore.dbversion_get.side_effect = Exception(\"fail\")\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        with patch.object(self.mock_logger, \"critical\") as mock_critical:\n            result = repo.get_db_version()\n            self.assertEqual(result, (None, None))\n            mock_critical.assert_called()\n\n    def test_026_get_directory_response_profiles_sync_load_profiles(self):\n        # Setup Directory with profiles_sync enabled and no error from handler_check\n        self.directory.config.profiles_sync = True\n        self.directory.config.acme_url = \"https://acme.example.com\"\n        self.directory.config.profiles_sync_interval = 1234\n        self.directory.config.async_mode = False\n        self.directory.config.profiles = {}\n        mock_cahandler_instance = MagicMock()\n        mock_cahandler_instance.__enter__.return_value = mock_cahandler_instance\n        mock_cahandler_instance.__exit__.return_value = None\n        mock_cahandler_instance.handler_check.return_value = None\n        mock_cahandler_instance.synchronize_profiles.return_value = {\n            \"profile\": \"loaded\"\n        }\n        self.directory.cahandler = MagicMock(return_value=mock_cahandler_instance)\n        # Ensure hasattr returns True for load_profiles\n        with patch.object(\n            mock_cahandler_instance,\n            \"load_profiles\",\n            wraps=mock_cahandler_instance.synchronize_profiles,\n        ):\n            resp = self.directory.get_directory_response()\n            self.assertEqual(self.directory.config.profiles, {\"profile\": \"loaded\"})\n            self.assertIn(\"newAuthz\", resp)\n\n    def test_027_get_directory_response_no_cahandler(self):\n        self.directory.cahandler = None\n        with patch.object(self.mock_logger, \"critical\") as mock_critical:\n            resp = self.directory.get_directory_response()\n            self.assertIn(\"error\", resp)\n            mock_critical.assert_called()\n\n    def test_028_profile_list_get_success(self):\n        mock_dbstore = MagicMock()\n        profiles_json = None\n        mock_dbstore.hkparameter_get.return_value = profiles_json\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        self.assertFalse(repo.profile_list_get())\n\n    def test_029_profile_list_get_success(self):\n        mock_dbstore = MagicMock()\n        profiles_json = '[{\"name\": \"profile1\"}, {\"name\": \"profile2\"}]'\n        mock_dbstore.hkparameter_get.return_value = profiles_json\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        result = repo.profile_list_get()\n        self.assertEqual(result, [{\"name\": \"profile1\"}, {\"name\": \"profile2\"}])\n\n    def test_030_profile_list_get_db_exception(self):\n        mock_dbstore = MagicMock()\n        mock_dbstore.hkparameter_get.side_effect = Exception(\"fail\")\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        with patch.object(self.mock_logger, \"critical\") as mock_critical:\n            result = repo.profile_list_get()\n            self.assertEqual(result, [])\n            mock_critical.assert_called()\n\n    def test_031_profile_list_get_json_error(self):\n        mock_dbstore = MagicMock()\n        # Use an invalid JSON string to ensure json.loads fails\n        mock_dbstore.hkparameter_get.return_value = \"{invalid_json: true]\"\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            result = repo.profile_list_get()\n            self.assertEqual(result, [])\n            mock_error.assert_called()\n\n    def test_032_profile_list_set_success(self):\n        mock_dbstore = MagicMock()\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        data_dic = {\"profiles\": [\"profile1\", \"profile2\"]}\n        repo.profile_list_set(data_dic)\n        mock_dbstore.hkparameter_add.assert_called_once_with(data_dic)\n\n    def test_033_profile_list_set_db_exception(self):\n        mock_dbstore = MagicMock()\n        mock_dbstore.hkparameter_add.side_effect = Exception(\"fail\")\n        repo = DirectoryRepository(mock_dbstore, self.mock_logger)\n        data_dic = {\"profiles\": [\"profile1\", \"profile2\"]}\n        with patch.object(self.mock_logger, \"critical\") as mock_critical:\n            repo.profile_list_set(data_dic)\n            mock_critical.assert_called()\n\n    def test_034_parse_cahandler_section_profiles_sync_exception(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        config_dic.getboolean.side_effect = Exception(\"fail\")\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            self.directory._parse_cahandler_section(config_dic)\n            mock_error.assert_any_call(\"profiles_sync not set: %s\", ANY)\n\n    def test_035_parse_cahandler_section_sets_acme_url(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        config_dic.getboolean.side_effect = lambda section, key, fallback=None: False\n        self.directory._parse_cahandler_section(config_dic)\n        self.assertEqual(self.directory.config.acme_url, \"https://acme.example.com\")\n\n    def test_036_parse_cahandler_section_profiles_sync_disabled(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        config_dic.getboolean.side_effect = lambda section, key, fallback=None: False\n        self.directory.config.profiles = {}\n        self.directory._parse_cahandler_section(config_dic)\n        self.assertFalse(self.directory.config.profiles_sync)\n\n    def test_037_parse_cahandler_section_profiles_sync_enabled_profiles_configured(\n        self,\n    ):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        # First call to getboolean returns True for profiles_sync\n        config_dic.getboolean.side_effect = (\n            lambda section, key, fallback=None: True\n            if key == \"profiles_sync\"\n            else False\n        )\n        self.directory.config.profiles = {\"profile\": \"data\"}\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            self.directory._parse_cahandler_section(config_dic)\n            self.assertFalse(self.directory.config.profiles_sync)\n            mock_error.assert_any_call(\n                \"Profiles are configured via acme_srv.cfg. Disabling profile sync.\"\n            )\n\n    def test_038_parse_cahandler_section_profiles_sync_enabled_no_acme_url(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = lambda k: {} if k == \"CAhandler\" else None\n        config_dic.getboolean.side_effect = (\n            lambda section, key, fallback=None: True\n            if key == \"profiles_sync\"\n            else False\n        )\n        self.directory.config.profiles = {}\n        self.directory.config.acme_url = None\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            self.directory._parse_cahandler_section(config_dic)\n            self.assertFalse(self.directory.config.profiles_sync)\n            mock_error.assert_any_call(\n                \"profiles_sync is set but no acme_url configured.\"\n            )\n\n    def test_039_parse_cahandler_section_profiles_sync_interval_set(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        config_dic.getboolean.side_effect = (\n            lambda section, key, fallback=None: True\n            if key == \"profiles_sync\"\n            else False\n        )\n        config_dic.getint.side_effect = (\n            lambda section, key, fallback=None: 1234\n            if key == \"profiles_sync_interval\"\n            else fallback\n        )\n        self.directory.config.profiles = {}\n        self.directory.config.acme_url = \"https://acme.example.com\"\n        self.directory.config.profiles_sync = False\n        self.directory._parse_cahandler_section(config_dic)\n        self.assertEqual(self.directory.config.profiles_sync_interval, 1234)\n\n    def test_040_parse_cahandler_section_profiles_sync_interval_error(self):\n        config_dic = MagicMock()\n        config_dic.__contains__.side_effect = lambda k: k == \"CAhandler\"\n        config_dic.__getitem__.side_effect = (\n            lambda k: {\"acme_url\": \"https://acme.example.com\"}\n            if k == \"CAhandler\"\n            else None\n        )\n        config_dic.getboolean.side_effect = (\n            lambda section, key, fallback=None: True\n            if key == \"profiles_sync\"\n            else False\n        )\n        config_dic.getint.side_effect = Exception(\"fail\")\n        self.directory.config.profiles = {}\n        self.directory.config.acme_url = \"https://acme.example.com\"\n        self.directory.config.profiles_sync = False\n        with patch.object(self.mock_logger, \"error\") as mock_error:\n            self.directory._parse_cahandler_section(config_dic)\n            mock_error.assert_any_call(\"profiles_sync_interval not set: %s\", ANY)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_django_update.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for django_update.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport os\nimport importlib\nfrom unittest.mock import patch, MagicMock, Mock, call\nfrom io import StringIO\n\n# Add the tools directory to the path\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), \"..\", \"tools\"))\n\n\nclass TestDjangoUpdate(unittest.TestCase):\n    \"\"\"test class for django_update.py\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        # Reset the global variables in django_update module\n        if \"django_update\" in sys.modules:\n            importlib.reload(sys.modules[\"django_update\"])\n\n    def tearDown(self):\n        \"\"\"cleanup after tests\"\"\"\n        # Remove django_update from modules to ensure clean state\n        if \"django_update\" in sys.modules:\n            del sys.modules[\"django_update\"]\n\n    def test_001_imports_and_setup(self):\n        \"\"\"test that imports and environment setup work\"\"\"\n        import django_update\n\n        # Check STATUS_LIST is defined\n        self.assertEqual(len(django_update.STATUS_LIST), 8)\n        self.assertIn(\"invalid\", django_update.STATUS_LIST)\n        self.assertIn(\"pending\", django_update.STATUS_LIST)\n\n    @patch(\"builtins.print\")\n    def test_002_setup_django_success(self, mock_print):\n        \"\"\"test successful Django setup\"\"\"\n        import django_update\n\n        mock_django = MagicMock()\n        mock_call_command = MagicMock()\n        mock_status = MagicMock()\n        mock_housekeeping = MagicMock()\n        mock_dbversion = \"1.0.0\"\n\n        with patch.dict(\n            \"sys.modules\",\n            {\n                \"django\": mock_django,\n                \"django.core.management\": MagicMock(call_command=mock_call_command),\n                \"acme_srv.models\": MagicMock(\n                    Status=mock_status, Housekeeping=mock_housekeeping\n                ),\n                \"acme_srv.version\": MagicMock(__dbversion__=mock_dbversion),\n            },\n        ):\n            with patch(\"django_update.django\", mock_django):\n                result = django_update.setup_django()\n\n        self.assertTrue(result)\n        mock_django.setup.assert_called_once()\n\n    @patch(\"builtins.print\")\n    def test_003_setup_django_import_error(self, mock_print):\n        \"\"\"test Django setup with import error\"\"\"\n        import django_update\n\n        with patch(\"builtins.__import__\", side_effect=ImportError(\"Django not found\")):\n            result = django_update.setup_django()\n\n        self.assertFalse(result)\n        # Check that error was printed to stderr\n        print_calls = mock_print.call_args_list\n        error_found = any(\n            \"Error importing Django modules\" in str(call) for call in print_calls\n        )\n        self.assertTrue(error_found)\n\n    @patch(\"builtins.print\")\n    def test_004_setup_django_general_error(self, mock_print):\n        \"\"\"test Django setup with general error\"\"\"\n        import django_update\n\n        mock_django = MagicMock()\n        mock_django.setup.side_effect = Exception(\"Setup failed\")\n\n        with patch.dict(\"sys.modules\", {\"django\": mock_django}):\n            with patch(\"django_update.django\", mock_django):\n                result = django_update.setup_django()\n\n        self.assertFalse(result)\n        # Check that error was printed\n        print_calls = mock_print.call_args_list\n        error_found = any(\n            \"Error during Django setup\" in str(call) for call in print_calls\n        )\n        self.assertTrue(error_found)\n\n    @patch(\"builtins.print\")\n    def test_005_run_migrations_success(self, mock_print):\n        \"\"\"test successful migration run\"\"\"\n        import django_update\n\n        mock_call_command = MagicMock()\n        django_update.call_command = mock_call_command\n\n        result = django_update.run_migrations()\n\n        self.assertTrue(result)\n        expected_calls = [\n            call(\"makemigrations\", interactive=False),\n            call(\"migrate\", interactive=False),\n        ]\n        mock_call_command.assert_has_calls(expected_calls)\n\n        # Check print calls\n        print_calls = [call[0][0] for call in mock_print.call_args_list]\n        self.assertIn(\"Running Django migrations...\", print_calls)\n        self.assertIn(\"Migrations created successfully.\", print_calls)\n        self.assertIn(\"Migrations applied successfully.\", print_calls)\n\n    @patch(\"builtins.print\")\n    def test_006_run_migrations_error(self, mock_print):\n        \"\"\"test migration run with error\"\"\"\n        import django_update\n\n        mock_call_command = MagicMock()\n        mock_call_command.side_effect = Exception(\"Migration failed\")\n        django_update.call_command = mock_call_command\n\n        result = django_update.run_migrations()\n\n        self.assertFalse(result)\n        # Check that error was printed\n        print_calls = mock_print.call_args_list\n        error_found = any(\n            \"Error during Django operations\" in str(call) for call in print_calls\n        )\n        self.assertTrue(error_found)\n\n    @patch(\"builtins.print\")\n    def test_007_update_status_fields_success(self, mock_print):\n        \"\"\"test successful status fields update\"\"\"\n        import django_update\n\n        mock_status = MagicMock()\n        mock_status.objects.update_or_create.return_value = (MagicMock(), True)\n        django_update.Status = mock_status\n\n        result = django_update.update_status_fields()\n\n        self.assertTrue(result)\n\n        # Check that update_or_create was called for each status\n        self.assertEqual(mock_status.objects.update_or_create.call_count, 8)\n\n        # Check specific calls\n        expected_calls = []\n        for status in django_update.STATUS_LIST:\n            expected_calls.append(call(name=status, defaults={\"name\": status}))\n        mock_status.objects.update_or_create.assert_has_calls(\n            expected_calls, any_order=True\n        )\n\n    @patch(\"builtins.print\")\n    def test_008_update_status_fields_partial_error(self, mock_print):\n        \"\"\"test status fields update with partial errors\"\"\"\n        import django_update\n\n        mock_status = MagicMock()\n        # Make the third call fail\n        mock_status.objects.update_or_create.side_effect = [\n            (MagicMock(), True),  # invalid - success\n            (MagicMock(), True),  # pending - success\n            Exception(\"Database error\"),  # ready - fail\n            (MagicMock(), True),  # processing - success\n            (MagicMock(), True),  # valid - success\n            (MagicMock(), True),  # expired - success\n            (MagicMock(), True),  # deactivated - success\n            (MagicMock(), True),  # revoked - success\n        ]\n        django_update.Status = mock_status\n\n        result = django_update.update_status_fields()\n\n        self.assertFalse(result)\n        # Check that error was printed\n        print_calls = mock_print.call_args_list\n        error_found = any(\n            \"Error updating status 'ready'\" in str(call) for call in print_calls\n        )\n        self.assertTrue(error_found)\n\n    @patch(\"builtins.print\")\n    def test_009_update_db_version_success(self, mock_print):\n        \"\"\"test successful database version update\"\"\"\n        import django_update\n\n        mock_housekeeping = MagicMock()\n        mock_housekeeping.objects.update_or_create.return_value = (MagicMock(), True)\n        django_update.Housekeeping = mock_housekeeping\n        django_update.__dbversion__ = \"2.0.0\"\n\n        result = django_update.update_db_version()\n\n        self.assertTrue(result)\n        mock_housekeeping.objects.update_or_create.assert_called_once_with(\n            name=\"dbversion\", defaults={\"name\": \"dbversion\", \"value\": \"2.0.0\"}\n        )\n\n    @patch(\"builtins.print\")\n    def test_010_update_db_version_error(self, mock_print):\n        \"\"\"test database version update with error\"\"\"\n        import django_update\n\n        mock_housekeeping = MagicMock()\n        mock_housekeeping.objects.update_or_create.side_effect = Exception(\"DB error\")\n        django_update.Housekeeping = mock_housekeeping\n        django_update.__dbversion__ = \"2.0.0\"\n\n        result = django_update.update_db_version()\n\n        self.assertFalse(result)\n        # Check that error was printed\n        print_calls = mock_print.call_args_list\n        error_found = any(\n            \"Error updating database version\" in str(call) for call in print_calls\n        )\n        self.assertTrue(error_found)\n\n    @patch(\"django_update.update_db_version\")\n    @patch(\"django_update.update_status_fields\")\n    @patch(\"django_update.run_migrations\")\n    @patch(\"django_update.setup_django\")\n    @patch(\"builtins.print\")\n    def test_011_main_all_success(\n        self, mock_print, mock_setup, mock_migrations, mock_status, mock_dbversion\n    ):\n        \"\"\"test main function with all operations successful\"\"\"\n        import django_update\n\n        mock_setup.return_value = True\n        mock_migrations.return_value = True\n        mock_status.return_value = True\n        mock_dbversion.return_value = True\n\n        result = django_update.main()\n\n        self.assertEqual(result, 0)\n        mock_setup.assert_called_once()\n        mock_migrations.assert_called_once()\n        mock_status.assert_called_once()\n        mock_dbversion.assert_called_once()\n\n        print_calls = [call[0][0] for call in mock_print.call_args_list]\n        self.assertIn(\"Django database update completed successfully.\", print_calls)\n\n    @patch(\"django_update.update_db_version\")\n    @patch(\"django_update.update_status_fields\")\n    @patch(\"django_update.run_migrations\")\n    @patch(\"django_update.setup_django\")\n    @patch(\"builtins.print\")\n    def test_012_main_setup_failure(\n        self, mock_print, mock_setup, mock_migrations, mock_status, mock_dbversion\n    ):\n        \"\"\"test main function with Django setup failure\"\"\"\n        import django_update\n\n        mock_setup.return_value = False\n\n        result = django_update.main()\n\n        self.assertEqual(result, 1)\n        mock_setup.assert_called_once()\n        mock_migrations.assert_not_called()\n        mock_status.assert_not_called()\n        mock_dbversion.assert_not_called()\n\n    @patch(\"django_update.update_db_version\")\n    @patch(\"django_update.update_status_fields\")\n    @patch(\"django_update.run_migrations\")\n    @patch(\"django_update.setup_django\")\n    @patch(\"builtins.print\")\n    def test_013_main_partial_failures(\n        self, mock_print, mock_setup, mock_migrations, mock_status, mock_dbversion\n    ):\n        \"\"\"test main function with partial failures\"\"\"\n        import django_update\n\n        mock_setup.return_value = True\n        mock_migrations.return_value = False  # Migration fails\n        mock_status.return_value = True\n        mock_dbversion.return_value = False  # DB version update fails\n\n        result = django_update.main()\n\n        self.assertEqual(result, 1)\n        mock_setup.assert_called_once()\n        mock_migrations.assert_called_once()\n        mock_status.assert_called_once()\n        mock_dbversion.assert_called_once()\n\n        print_calls = [call[0][0] for call in mock_print.call_args_list]\n        self.assertIn(\"Django database update completed with errors.\", print_calls)\n\n    @patch(\"django_update.main\")\n    @patch(\"django_update.sys.exit\")\n    def test_014_main_entry_point(self, mock_exit, mock_main):\n        \"\"\"test main entry point when script is run directly\"\"\"\n        mock_main.return_value = 0\n\n        # Simulate running the script directly\n        import django_update\n\n        # Manually trigger the if __name__ == \"__main__\" block\n        if True:  # Simulating __name__ == \"__main__\"\n            django_update.sys.exit(django_update.main())\n\n        mock_main.assert_called_once()\n        mock_exit.assert_called_once_with(0)\n\n    @patch(\"django_update.main\")\n    @patch(\"django_update.sys.exit\")\n    def test_015_main_entry_point_with_error(self, mock_exit, mock_main):\n        \"\"\"test main entry point when script encounters error\"\"\"\n        mock_main.return_value = 1\n\n        # Simulate running the script directly with error\n        import django_update\n\n        # Manually trigger the if __name__ == \"__main__\" block\n        if True:  # Simulating __name__ == \"__main__\"\n            django_update.sys.exit(django_update.main())\n\n        mock_main.assert_called_once()\n        mock_exit.assert_called_once_with(1)\n\n    def test_016_status_list_completeness(self):\n        \"\"\"test that STATUS_LIST contains all expected status values\"\"\"\n        import django_update\n\n        expected_statuses = [\n            \"invalid\",\n            \"pending\",\n            \"ready\",\n            \"processing\",\n            \"valid\",\n            \"expired\",\n            \"deactivated\",\n            \"revoked\",\n        ]\n\n        self.assertEqual(django_update.STATUS_LIST, expected_statuses)\n        self.assertEqual(len(django_update.STATUS_LIST), 8)\n\n    @patch(\"builtins.print\")\n    def test_017_update_status_fields_print_messages(self, mock_print):\n        \"\"\"test that update_status_fields prints the correct messages\"\"\"\n        import django_update\n\n        mock_status = MagicMock()\n        mock_status.objects.update_or_create.return_value = (MagicMock(), True)\n        django_update.Status = mock_status\n\n        django_update.update_status_fields()\n\n        print_calls = [call[0][0] for call in mock_print.call_args_list]\n        self.assertIn(\"adding additional status fields to table...\", print_calls)\n\n    @patch(\"builtins.print\")\n    def test_018_update_db_version_print_messages(self, mock_print):\n        \"\"\"test that update_db_version prints the correct messages\"\"\"\n        import django_update\n\n        mock_housekeeping = MagicMock()\n        mock_housekeeping.objects.update_or_create.return_value = (MagicMock(), True)\n        django_update.Housekeeping = mock_housekeeping\n        django_update.__dbversion__ = \"3.0.0\"\n\n        django_update.update_db_version()\n\n        print_calls = [call[0][0] for call in mock_print.call_args_list]\n        self.assertIn(\"update dbversion to 3.0.0...\", print_calls)\n        self.assertIn(\"Database version updated successfully.\", print_calls)\n\n    def test_019_global_variables_initialization(self):\n        \"\"\"test that global variables are properly initialized\"\"\"\n        import django_update\n\n        # Test that global variables exist and are initially None\n        self.assertIsNone(django_update.django)\n        self.assertIsNone(django_update.call_command)\n        self.assertIsNone(django_update.Status)\n        self.assertIsNone(django_update.Housekeeping)\n        self.assertIsNone(django_update.__dbversion__)\n\n    @patch(\"builtins.print\")\n    def test_020_setup_django_sets_globals(self, mock_print):\n        \"\"\"test that setup_django properly sets global variables\"\"\"\n        import django_update\n\n        mock_django = MagicMock()\n        mock_call_command = MagicMock()\n        mock_status = MagicMock()\n        mock_housekeeping = MagicMock()\n        mock_dbversion = \"4.0.0\"\n\n        with patch.dict(\n            \"sys.modules\",\n            {\n                \"django\": mock_django,\n                \"django.core.management\": MagicMock(call_command=mock_call_command),\n                \"acme_srv.models\": MagicMock(\n                    Status=mock_status, Housekeeping=mock_housekeeping\n                ),\n                \"acme_srv.version\": MagicMock(__dbversion__=mock_dbversion),\n            },\n        ):\n            result = django_update.setup_django()\n\n        self.assertTrue(result)\n        # Check that global variables are set\n        self.assertEqual(django_update.call_command, mock_call_command)\n        self.assertEqual(django_update.Status, mock_status)\n        self.assertEqual(django_update.Housekeeping, mock_housekeeping)\n        self.assertEqual(django_update.__dbversion__, mock_dbversion)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_eabfile_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom OpenSSL import crypto\nfrom unittest.mock import patch, Mock, MagicMock, mock_open\nimport requests\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.eab_handler.file_handler import EABhandler\n\n        self.eabhandler = EABhandler(self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.eab_handler.file_handler.EABhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.eabhandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.eab_handler.file_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - empty dictionary\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.file_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - no values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"foo\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.file_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.file_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"key_file\": \"key_file\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertEqual(\"key_file\", self.eabhandler.key_file)\n\n    def test_007_mac_key_get(self):\n        \"\"\"test mac_key_get without file specified\"\"\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_008_mac_key_get(self):\n        \"\"\"test mac_key_get with file but no kid\"\"\"\n        self.eabhandler.key_file = \"file\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_009_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader return bogus values\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [\"foo\", \"bar\"]\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_010_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader return match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [{\"eab_kid\": \"kid\", \"eab_mac\": \"mac\"}]\n        self.assertEqual(\"mac\", self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_011_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader no match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [{\"eab_kid\": \"kid1\", \"eab_mac\": \"mac\"}]\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_012_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get check break after first match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [\n            {\"eab_kid\": \"kid1\", \"eab_mac\": \"mac1\"},\n            {\"eab_kid\": \"kid2\", \"eab_mac\": \"mac2\"},\n        ]\n        self.assertEqual(\"mac1\", self.eabhandler.mac_key_get(\"kid1\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_013_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get match in the 2nd record\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [\n            {\"eab_kid\": \"kid1\", \"eab_mac\": \"mac1\"},\n            {\"eab_kid\": \"kid2\", \"eab_mac\": \"mac2\"},\n        ]\n        self.assertEqual(\"mac2\", self.eabhandler.mac_key_get(\"kid2\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_014_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader no eab_kid\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [{\"eab__kid\": \"kid\", \"eab_mac\": \"mac\"}]\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_015_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader no mac but match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.return_value = [{\"eab_kid\": \"kid\", \"_eab_mac\": \"mac\"}]\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"csv.DictReader\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_016_mac_key_get(self, mock_csv):\n        \"\"\"test mac_key_get csv reader no mac but match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_csv.side_effect = Exception(\"ex_mock_csv\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load EAB key file: ex_mock_csv\", lcm.output\n        )\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_eabjson_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom OpenSSL import crypto\nfrom unittest.mock import patch, Mock, MagicMock, mock_open\nimport requests\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.eab_handler.json_handler import EABhandler\n\n        self.eabhandler = EABhandler(self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.eab_handler.json_handler.EABhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.eabhandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.eab_handler.json_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - empty dictionary\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.json_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"foo\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.json_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.json_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"key_file\": \"key_file\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertEqual(\"key_file\", self.eabhandler.key_file)\n\n    def test_007_mac_key_get(self):\n        \"\"\"test mac_key_get without file specified\"\"\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_008_mac_key_get(self):\n        \"\"\"test mac_key_get with file but no kid\"\"\"\n        self.eabhandler.key_file = \"file\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"json.load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_009_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json reader return bogus values\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"foo\", \"bar\"}\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"json.load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_010_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"kid\": \"mac\"}\n        self.assertEqual(\"mac\", self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"json.load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_011_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json no match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"kid1\": \"mac\"}\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"json.load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_012_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json load exception\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.side_effect = Exception(\"ex_json_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load EAB key file: ex_json_load\", lcm.output\n        )\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_eabkid_profile_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom OpenSSL import crypto\nfrom unittest.mock import patch, Mock, MagicMock, mock_open\nimport requests\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.eab_handler.kid_profile_handler import EABhandler\n\n        self.eabhandler = EABhandler(self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.eabhandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.eab_handler.kid_profile_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - empty dictionary\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.kid_profile_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"foo\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.kid_profile_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.key_file)\n\n    @patch(\"examples.eab_handler.kid_profile_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"key_file\": \"key_file\"}\n        mock_load_cfg.return_value = parser\n        self.eabhandler._config_load()\n        self.assertEqual(\"key_file\", self.eabhandler.key_file)\n\n    def test_007_mac_key_get(self):\n        \"\"\"test mac_key_get without file specified\"\"\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_008_mac_key_get(self):\n        \"\"\"test mac_key_get with file but no kid\"\"\"\n        self.eabhandler.key_file = \"file\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.keyfile_content_load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_009_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json reader return bogus values\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"foo\", \"bar\"}\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.keyfile_content_load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_010_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"kid\": {\"hmac\": \"mac\", \"foo\": \"bar\"}}\n        self.assertEqual(\"mac\", self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.keyfile_content_load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_011_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json no match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"kid1\": \"mac\"}\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.keyfile_content_load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_012_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json load exception\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.side_effect = Exception(\"ex_json_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to retrieve MAC key for kid 'kid': ex_json_load\",\n            lcm.output,\n        )\n\n    @patch(\"json.load\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_013_mac_key_get(self, mock_json):\n        \"\"\"test mac_key_get json match\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_json.return_value = {\"kid\": {\"foo\": \"bar\"}}\n        self.assertFalse(self.eabhandler.mac_key_get(\"kid\"))\n\n    def test_014_wllist_check(self):\n        \"\"\"CAhandler._wllist_check failed check as empty entry\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = None\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_015_wllist_check(self):\n        \"\"\"CAhandler._wllist_check check against empty list\"\"\"\n        list_ = []\n        entry = \"host.bar.foo\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    def test_016_wllist_check(self):\n        \"\"\"CAhandler._wllist_check successful check against 1st element of a list\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    def test_017_wllist_check(self):\n        \"\"\"CAhandler._wllist_check unsuccessful as endcheck failed\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo.bar_\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_018_wllist_check(self):\n        \"\"\"CAhandler._wllist_check successful without $\"\"\"\n        list_ = [\"bar.foo\", \"foo.bar$\"]\n        entry = \"host.bar.foo.bar_\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    def test_019_wllist_check(self):\n        \"\"\"CAhandler._wllist_check wildcard check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    def test_020_wllist_check(self):\n        \"\"\"CAhandler._wllist_check failed wildcard check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"*.bar.foo_\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_021_wllist_check(self):\n        \"\"\"CAhandler._wllist_check not end check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo gna\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_022_wllist_check(self):\n        \"\"\"CAhandler._wllist_check $ at the end\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo$\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_023_wllist_check(self):\n        \"\"\"CAhandler._wllist_check check against empty list flip\"\"\"\n        list_ = []\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_, True))\n\n    def test_024_wllist_check(self):\n        \"\"\"CAhandler._wllist_check flip successful check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_, True))\n\n    def test_025_wllist_check(self):\n        \"\"\"CAhandler._wllist_check flip unsuccessful check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_, True))\n\n    def test_026_wllist_check(self):\n        \"\"\"CAhandler._wllist_check unsuccessful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"\\*.bar.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.eabhandler._wllist_check(entry, list_))\n\n    def test_027_wllist_check(self):\n        \"\"\"CAhandler._wllist_check successful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"\\*.bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    def test_028_wllist_check(self):\n        \"\"\"CAhandler._wllist_check successful whildcard in list but not in string\"\"\"\n        list_ = [\"foo.bar$\", \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        self.assertTrue(self.eabhandler._wllist_check(entry, list_))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.csr_san_get\")\n    def test_029_chk_san_lists_get(self, mock_san):\n        \"\"\"CAhandler._chk_san_lists_get()\"\"\"\n        csr = \"csr\"\n        mock_san.return_value = [\"dns:foo.bar\", \"dns:bar.foo\"]\n        self.assertEqual(\n            ([\"foo.bar\", \"bar.foo\"], []), self.eabhandler._chk_san_lists_get(csr)\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.csr_san_get\")\n    def test_030_chk_san_lists_get(self, mock_san):\n        \"\"\"CAhandler._chk_san_lists_get()\"\"\"\n        csr = \"csr\"\n        mock_san.return_value = [\"dns:foo.bar\", \"bar.foo\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                ([\"foo.bar\"], [False]), self.eabhandler._chk_san_lists_get(csr)\n            )\n        self.assertIn(\n            \"INFO:test_a2c:SAN list parsing failed at entry: bar.foo\",\n            lcm.output,\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.csr_san_get\")\n    def test_031_chk_san_lists_get(self, mock_san):\n        \"\"\"CAhandler._chk_san_lists_get()\"\"\"\n        csr = \"csr\"\n        mock_san.return_value = None\n        self.assertEqual(([], []), self.eabhandler._chk_san_lists_get(csr))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.csr_cn_get\")\n    def test_032_cn_add(self, mock_cnget):\n        \"\"\"CAhandler._cn_add()\"\"\"\n        csr = \"csr\"\n        san_list = [\"foo.bar\", \"bar.foo\"]\n        mock_cnget.return_value = \"foobar.bar\"\n        self.assertEqual(\n            [\"foo.bar\", \"bar.foo\", \"foobar.bar\"], self.eabhandler._cn_add(csr, san_list)\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.csr_cn_get\")\n    def test_033_cn_add(self, mock_cnget):\n        \"\"\"CAhandler._cn_add()\"\"\"\n        csr = \"csr\"\n        san_list = [\"foo.bar\", \"bar.foo\"]\n        mock_cnget.return_value = \"bar.foo\"\n        self.assertEqual([\"foo.bar\", \"bar.foo\"], self.eabhandler._cn_add(csr, san_list))\n\n    @patch(\"builtins.open\", mock_open(read_data='{\"foo\": \"bar\"}'), create=True)\n    def test_034_key_file_load(self):\n        \"\"\"CAhandler._cn_add()\"\"\"\n        self.eabhandler.key_file = \"file\"\n        self.assertEqual({\"foo\": \"bar\"}, self.eabhandler.key_file_load())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foobar:\\n  foo: foobar\"), create=True)\n    def test_035_key_file_load(self):\n        \"\"\"CAhandler._key_file_load()\"\"\"\n        self.eabhandler.key_file = \"file\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                {\"foobar\": {\"foo\": \"foobar\"}}, self.eabhandler.key_file_load()\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse key file content as JSON: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data='{\"foo\": \"bar\"}'), create=True)\n    def test_036_key_file_load(self):\n        \"\"\"CAhandler._key_file_load()\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.key_file_load())\n        self.assertIn(\n            \"ERROR:test_a2c:No key_file specified for EAB profile loading.\",\n            lcm.output,\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foobar: =\"), create=True)\n    def test_037_key_file_load(self):\n        \"\"\"CAhandler._key_file_load()\"\"\"\n        self.eabhandler.key_file = \"file\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.key_file_load())\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse key file content as JSON: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse key file content as YAML: could not determine a constructor for the tag 'tag:yaml.org,2002:value'\\n  in \\\"<unicode string>\\\", line 1, column 9:\\n    foobar: =\\n            ^\",\n            lcm.output,\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.keyfile_content_load\")\n    @patch(\"builtins.open\", mock_open(read_data='{\"foo\": \"bar\"}'), create=True)\n    def test_038_key_file_load(self, mock_load):\n        \"\"\"CAhandler._key_file_load()\"\"\"\n        self.eabhandler.key_file = \"file\"\n        mock_load.side_effect = Exception(\"ex_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.key_file_load())\n        self.assertIn(\"ERROR:test_a2c:Failed to load key file: ex_load\", lcm.output)\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._chk_san_lists_get\")\n    def test_039_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [True, True]\n        self.assertFalse(\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"])\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._chk_san_lists_get\")\n    def test_040_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [False])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [True, True]\n        self.assertEqual(\n            \"Either CN or SANs are not allowed by profile\",\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"]),\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler._chk_san_lists_get\")\n    def test_041_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [False, True]\n        self.assertEqual(\n            \"Either CN or SANs are not allowed by profile\",\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"]),\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.key_file_load\")\n    def test_042_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertEqual(\n            {\"foo_parameter\": \"bar_parameter\"}, self.eabhandler.eab_profile_get(\"csr\")\n        )\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.key_file_load\")\n    def test_043_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler1\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.key_file_load\")\n    def test_044_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"1order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.key_file_load\")\n    def test_045_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid1\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.kid_profile_handler.EABhandler.key_file_load\")\n    def test_046_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.side_effect = Exception(\"ex_db_lookup\")\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Database error while retrieving eab_kid: ex_db_lookup\",\n            lcm.output,\n        )\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_eabsql_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, MagicMock\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestEABHandler(unittest.TestCase):\n    \"\"\"test class for sql_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unit test\"\"\"\n        import sys\n        import types\n\n        sys.modules[\"psycopg2\"] = types.ModuleType(\"psycopg2\")\n        sys.modules[\"psycopg2\"].connect = MagicMock()\n        mssql_mock = types.ModuleType(\"mssql_python\")\n\n        def dummy_connect(*args, **kwargs):\n            return None\n\n        mssql_mock.connect = dummy_connect\n        sys.modules[\"mssql_python\"] = mssql_mock\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.eab_handler.sql_handler import EABhandler\n\n        self.eabhandler = EABhandler(self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._config_load\")\n    def test_002__enter__(self, mock_config_load):\n        \"\"\"test enter called\"\"\"\n        mock_config_load.return_value = True\n        self.eabhandler.__enter__()\n        self.assertTrue(mock_config_load.called)\n\n    @patch(\"examples.eab_handler.sql_handler.load_config\")\n    def test_003_config_load(self, mock_config_load):\n        \"\"\"test _config_load - empty dictionary\"\"\"\n        parser = configparser.ConfigParser()\n        mock_config_load.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.db_system)\n        self.assertFalse(self.eabhandler.db_host)\n        self.assertFalse(self.eabhandler.db_name)\n        self.assertFalse(self.eabhandler.db_user)\n        self.assertFalse(self.eabhandler.db_password)\n\n    @patch(\"examples.eab_handler.sql_handler.load_config\")\n    def test_004_config_load(self, mock_load_config_load):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"foo\"] = {\"foo\": \"bar\"}\n        mock_load_config_load.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.db_system)\n        self.assertFalse(self.eabhandler.db_host)\n        self.assertFalse(self.eabhandler.db_name)\n        self.assertFalse(self.eabhandler.db_user)\n        self.assertFalse(self.eabhandler.db_password)\n\n    @patch(\"examples.eab_handler.sql_handler.load_config\")\n    def test_005_config_load(self, mock_config_load):\n        \"\"\"test _config_load - bogus values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\"}\n        mock_config_load.return_value = parser\n        self.eabhandler._config_load()\n        self.assertFalse(self.eabhandler.db_system)\n        self.assertFalse(self.eabhandler.db_host)\n        self.assertFalse(self.eabhandler.db_name)\n        self.assertFalse(self.eabhandler.db_user)\n        self.assertFalse(self.eabhandler.db_password)\n\n    @patch(\"examples.eab_handler.sql_handler.load_config\")\n    def test_006_config_load(self, mock_config_load):\n        \"\"\"test _config_load - valid values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"db_system\": \"db_system\"}\n        mock_config_load.return_value = parser\n        self.eabhandler._config_load()\n        self.assertEqual(\"db_system\", self.eabhandler.db_system)\n        self.assertFalse(self.eabhandler.db_host)\n        self.assertFalse(self.eabhandler.db_name)\n        self.assertFalse(self.eabhandler.db_user)\n        self.assertFalse(self.eabhandler.db_password)\n\n    @patch(\"examples.eab_handler.sql_handler.load_config\")\n    def test_007_config_load(self, mock_config_load):\n        \"\"\"test _config_load - valid values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"db_system\": \"db_system\",\n            \"db_host\": \"db_host\",\n            \"db_name\": \"db_name\",\n            \"db_user\": \"db_user\",\n            \"db_password\": \"db_password\",\n        }\n        mock_config_load.return_value = parser\n        self.eabhandler._config_load()\n        self.assertEqual(\"db_system\", self.eabhandler.db_system)\n        self.assertEqual(\"db_host\", self.eabhandler.db_host)\n        self.assertEqual(\"db_name\", self.eabhandler.db_name)\n        self.assertEqual(\"db_user\", self.eabhandler.db_user)\n        self.assertEqual(\"db_password\", self.eabhandler.db_password)\n\n    def test_008_mac_key_get(self):\n        \"\"\"test mac_key_get without db parameters specified\"\"\"\n        self.assertFalse(self.eabhandler.mac_key_get(None))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._chk_san_lists_get\")\n    def test_009_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [True, True]\n        self.assertFalse(\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"])\n        )\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._chk_san_lists_get\")\n    def test_010_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [False])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [True, True]\n        self.assertEqual(\n            \"Either CN or SANs are not allowed by profile\",\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"]),\n        )\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._wllist_check\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._cn_add\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._chk_san_lists_get\")\n    def test_011_allowed_domains_check(self, mock_san, mock_cn, mock_wlc):\n        \"\"\"test EABhanlder._allowed_domains_check()\"\"\"\n        mock_san.return_value = ([\"foo\"], [])\n        mock_cn.return_value = [\"foo\", \"bar\"]\n        mock_wlc.side_effect = [False, True]\n        self.assertEqual(\n            \"Either CN or SANs are not allowed by profile\",\n            self.eabhandler._allowed_domains_check(\"csr\", [\"domain\", \"list\"]),\n        )\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_012_eab_profile_get(self, mock_key_file_load):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_key_file_load.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertEqual(\n            {\"foo_parameter\": \"bar_parameter\"}, self.eabhandler.eab_profile_get(\"csr\")\n        )\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_013_eab_profile_get(self, mock_key_file_load):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_key_file_load.return_value = {\n            \"eab_kid\": {\"cahandler_invalid\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_014_eab_profile_get(self, mock_key_file_load):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_key_file_load.return_value = {\n            \"eab_kid\": {\"cahandler1\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"1order__account__eab_kid\": \"eab_kid\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_015_eab_profile_get(self, mock_key_file_load):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_key_file_load.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.return_value = {\n            \"foo\": \"bar\",\n            \"order__account__eab_kid\": \"eab_kid1\",\n        }\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_016_eab_profile_get(self, mock_prof):\n        \"\"\"test EABhandler._eab_profile_get()\"\"\"\n        mock_prof.return_value = {\n            \"eab_kid\": {\"cahandler\": {\"foo_parameter\": \"bar_parameter\"}}\n        }\n        models_mock = MagicMock()\n        models_mock.DBstore().certificate_lookup.side_effect = Exception(\"ex_db_lookup\")\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eabhandler.eab_profile_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Database error while retrieving eab_kid: ex_db_lookup\",\n            lcm.output,\n        )\n\n    def test_017_chk_san_lists_get_empty(self):\n        # Should return empty lists for empty input\n        result = self.eabhandler._chk_san_lists_get(None)\n        self.assertEqual(result, ([], []))\n\n    @patch(\"examples.eab_handler.sql_handler.csr_san_get\")\n    def test_018_chk_san_lists_get_value(self, mock_csr_san_get):\n        # Should return empty lists for empty input\n        mock_csr_san_get.return_value = [\"dns:example.com\", \"dns:example.org\"]\n        result = self.eabhandler._chk_san_lists_get(\"csr\")\n        self.assertEqual(result, ([\"example.com\", \"example.org\"], []))\n\n    @patch(\"examples.eab_handler.sql_handler.csr_san_get\")\n    def test_019_chk_san_lists_get_value(self, mock_csr_san_get):\n        # Should return empty lists for empty input\n        mock_csr_san_get.return_value = [\"example.com\", \"example.org\"]  #\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                [False, False], self.eabhandler._chk_san_lists_get(\"csr\")[1]\n            )\n        self.assertIn(\n            \"INFO:test_a2c:SAN list parsing failed at entry: example.com\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:SAN list parsing failed at entry: example.org\", lcm.output\n        )\n\n    @patch(\"examples.eab_handler.sql_handler.csr_cn_get\")\n    def test_021_cn_add_cn_not_in_sans(self, mock_csr_cn_get):\n        \"\"\"CN present and not in SANs: should append CN\"\"\"\n        mock_csr_cn_get.return_value = \"example.com\"\n        result = self.eabhandler._cn_add(\"dummy_csr\", [\"test.com\"])\n        self.assertIn(\"example.com\", result)\n        self.assertIn(\"test.com\", result)\n        self.assertEqual(len(result), 2)\n\n    @patch(\"examples.eab_handler.sql_handler.csr_cn_get\")\n    def test_022_cn_add_cn_already_in_sans(self, mock_csr_cn_get):\n        \"\"\"CN present and already in SANs: should not duplicate CN\"\"\"\n        mock_csr_cn_get.return_value = \"example.com\"\n        result = self.eabhandler._cn_add(\"dummy_csr\", [\"example.com\", \"test.com\"])\n        self.assertIn(\"example.com\", result)\n        self.assertIn(\"test.com\", result)\n        self.assertEqual(len(result), 2)\n\n    @patch(\"examples.eab_handler.sql_handler.csr_cn_get\")\n    def test_023_cn_add_no_cn(self, mock_csr_cn_get):\n        \"\"\"No CN present: should not modify SANs\"\"\"\n        mock_csr_cn_get.return_value = None\n        result = self.eabhandler._cn_add(\"dummy_csr\", [\"test.com\"])\n        self.assertEqual(result, [\"test.com\"])\n\n    def test_024_list_regex_check_match(self):\n        \"\"\"Entry matches regex: should return True\"\"\"\n        result = self.eabhandler._list_regex_check(\"example.com\", [\"example\\\\.com\"])\n        self.assertTrue(result)\n\n    def test_025_list_regex_check_no_match(self):\n        \"\"\"Entry does not match regex: should return False\"\"\"\n        result = self.eabhandler._list_regex_check(\"example.com\", [\"test\\\\.com\"])\n        self.assertFalse(result)\n\n    def test_026_list_regex_check_wildcard(self):\n        \"\"\"Entry matches wildcard regex: should return True\"\"\"\n        result = self.eabhandler._list_regex_check(\n            \"sub.example.com\", [\"*.example\\\\.com\"]\n        )\n        self.assertTrue(result)\n\n    def test_027_wllist_check_match(self):\n        \"\"\"Entry matches list: should return True\"\"\"\n        result = self.eabhandler._wllist_check(\"example.com\", [\"example\\\\.com\"])\n        self.assertTrue(result)\n\n    def test_028_wllist_check_empty_list(self):\n        \"\"\"Empty list: should return True\"\"\"\n        result = self.eabhandler._wllist_check(\"example.com\", [])\n        self.assertTrue(result)\n\n    def test_029_wllist_check_toggle(self):\n        \"\"\"Toggle: should invert result\"\"\"\n        result = self.eabhandler._wllist_check(\n            \"example.com\", [\"example\\\\.com\"], toggle=True\n        )\n        self.assertFalse(result)\n\n    def test_030_wllist_check_no_match(self):\n        \"\"\"Entry does not match list: should return False\"\"\"\n        result = self.eabhandler._wllist_check(\"example.com\", [\"test\\\\.com\"])\n        self.assertFalse(result)\n\n    def test_031_key_file_load_no_db_params(self):\n        \"\"\"No DB params: should return empty dict\"\"\"\n        self.eabhandler.db_host = None\n        self.eabhandler.db_name = None\n        self.eabhandler.db_user = None\n        self.eabhandler.db_password = None\n        result = self.eabhandler.key_file_load()\n        self.assertEqual(result, {})\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._load_mssql_profiles\")\n    def test_032_key_file_load_mssql(self, mock_load_mssql):\n        \"\"\"MSSQL: should call _load_mssql_profiles and return its result\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        self.eabhandler.db_system = \"mssql\"\n        mock_load_mssql.return_value = {\"key\": \"profile\"}\n        result = self.eabhandler.key_file_load()\n        self.assertEqual(result, {\"key\": \"profile\"})\n        mock_load_mssql.assert_called_once()\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._load_postgres_profiles\")\n    def test_033_key_file_load_postgres(self, mock_load_postgres):\n        \"\"\"Postgres: should call _load_postgres_profiles and return its result\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        self.eabhandler.db_system = \"postgres\"\n        mock_load_postgres.return_value = {\"key\": \"profile\"}\n        result = self.eabhandler.key_file_load()\n        self.assertEqual(result, {\"key\": \"profile\"})\n        mock_load_postgres.assert_called_once()\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._load_mssql_profiles\")\n    @patch(\"examples.eab_handler.sql_handler.EABhandler._load_postgres_profiles\")\n    def test_034_key_file_load_error(self, mock_postgres, mock_mssql):\n        \"\"\"Invalid db_system: should return empty dict\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        self.eabhandler.db_system = \"invalid\"\n        mock_postgres.return_value = {}\n        mock_mssql.return_value = {}\n        result = self.eabhandler.key_file_load()\n        self.assertEqual(result, {})\n\n    @patch(\"examples.eab_handler.sql_handler.connect\")\n    def test_035_load_mssql_profiles_success(self, mock_connect):\n        \"\"\"Successful fetch: should return dict with profiles\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        # Mock MSSQL connection and cursor\n        mock_conn = MagicMock()\n        mock_cursor = MagicMock()\n        mock_conn.cursor.return_value = mock_cursor\n        mock_cursor.fetchall.return_value = [\n            MagicMock(key_id=\"id1\", profile=\"profile1\"),\n            MagicMock(key_id=\"id2\", profile=\"profile2\"),\n        ]\n        mock_connect.return_value = mock_conn\n        result = self.eabhandler._load_mssql_profiles(\"SELECT ...\")\n        self.assertEqual(result, {\"id1\": \"profile1\", \"id2\": \"profile2\"})\n        mock_conn.close.assert_called_once()\n\n    @patch(\"examples.eab_handler.sql_handler.connect\")\n    def test_036_load_mssql_profiles_empty(self, mock_connect):\n        \"\"\"Empty result: should return empty dict\"\"\"\n        mock_conn = MagicMock()\n        mock_cursor = MagicMock()\n        mock_conn.cursor.return_value = mock_cursor\n        mock_cursor.fetchall.return_value = []\n        mock_connect.return_value = mock_conn\n        result = self.eabhandler._load_mssql_profiles(\"SELECT ...\")\n        self.assertEqual(result, {})\n\n    @patch(\"examples.eab_handler.sql_handler.connect\")\n    def test_037_load_mssql_profiles_exception(self, mock_connect):\n        \"\"\"Exception: should log error and return empty dict\"\"\"\n        mock_connect.side_effect = Exception(\"connection error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.eabhandler._load_mssql_profiles(\"SELECT ...\")\n        self.assertEqual(result, {})\n        self.assertTrue(any(\"error\" in msg.lower() for msg in lcm.output))\n\n    @patch(\"examples.eab_handler.sql_handler.psycopg2.connect\")\n    def test_038_load_postgres_profiles_success(self, mock_connect):\n        \"\"\"Successful fetch: should return dict with profiles\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        mock_conn = MagicMock()\n        mock_cursor = MagicMock()\n        mock_conn.cursor.return_value = mock_cursor\n        mock_cursor.fetchall.return_value = [(\"id1\", \"profile1\"), (\"id2\", \"profile2\")]\n        mock_connect.return_value = mock_conn\n        result = self.eabhandler._load_postgres_profiles(\"SELECT ...\")\n        self.assertEqual(result, {\"id1\": \"profile1\", \"id2\": \"profile2\"})\n        mock_conn.close.assert_called_once()\n\n    @patch(\"examples.eab_handler.sql_handler.psycopg2.connect\")\n    def test_039_load_postgres_profiles_empty(self, mock_connect):\n        \"\"\"Empty result: should return empty dict\"\"\"\n        mock_conn = MagicMock()\n        mock_conn.close = MagicMock()\n        mock_conn.__bool__.return_value = True\n        mock_cursor = MagicMock()\n        mock_conn.cursor.return_value = mock_cursor\n        mock_cursor.fetchall.return_value = []\n        mock_connect.return_value = mock_conn\n        result = self.eabhandler._load_postgres_profiles(\"SELECT ...\")\n        self.assertEqual(result, {})\n        self.assertTrue(mock_conn.close.called)\n\n    @patch(\"examples.eab_handler.sql_handler.psycopg2.connect\")\n    def test_040_load_postgres_profiles_exception(self, mock_connect):\n        \"\"\"Exception: should log error and return empty dict\"\"\"\n        mock_connect.side_effect = Exception(\"connection error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.eabhandler._load_postgres_profiles(\"SELECT ...\")\n        self.assertEqual(result, {})\n        self.assertTrue(any(\"error\" in msg.lower() for msg in lcm.output))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_041_mac_key_get_valid(self, mock_key_file_load):\n        \"\"\"Valid key: should return mac_key\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        mock_key_file_load.return_value = {\"key1\": \"mac_value\"}\n        result = self.eabhandler.mac_key_get(\"key1\")\n        self.assertEqual(result, \"mac_value\")\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_042_mac_key_get_missing_key(self, mock_key_file_load):\n        \"\"\"Missing key: should return None\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        mock_key_file_load.return_value = {\"key1\": \"mac_value\"}\n        result = self.eabhandler.mac_key_get(\"key2\")\n        self.assertIsNone(result)\n\n    def test_043_mac_key_get_missing_db_params(self):\n        \"\"\"Missing DB params: should return None and log error\"\"\"\n        self.eabhandler.db_host = None\n        self.eabhandler.db_name = None\n        self.eabhandler.db_user = None\n        self.eabhandler.db_password = None\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.eabhandler.mac_key_get(\"key1\")\n        self.assertIsNone(result)\n        self.assertTrue(any(\"error\" in msg.lower() for msg in lcm.output))\n\n    @patch(\"examples.eab_handler.sql_handler.EABhandler.key_file_load\")\n    def test_044_mac_key_get_exception(self, mock_key_file_load):\n        \"\"\"Exception: should return None and log error\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        mock_key_file_load.side_effect = Exception(\"lookup error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.eabhandler.mac_key_get(\"key1\")\n        self.assertIsNone(result)\n        self.assertTrue(any(\"error\" in msg.lower() for msg in lcm.output))\n\n    def test_045_eab_kid_get_exception(self):\n        \"\"\"Exception branch: should log error and return None\"\"\"\n        self.eabhandler.db_host = \"host\"\n        self.eabhandler.db_name = \"name\"\n        self.eabhandler.db_user = \"user\"\n        self.eabhandler.db_password = \"pass\"\n        dbstore_mock = MagicMock()\n        dbstore_mock.side_effect = Exception(\"db error\")\n        sys.modules[\"acme_srv.db_handler\"] = MagicMock(DBstore=dbstore_mock)\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.eabhandler.eab_kid_get(\"csr\")\n        self.assertIsNone(result)\n        self.assertTrue(\n            any(\"Database error while retrieving eab_kid\" in msg for msg in lcm.output)\n        )\n\n\nif __name__ == \"__main__\":\n\n    if os.path.exists(\"acme_test.db\"):\n        os.remove(\"acme_test.db\")\n    unittest.main()\n"
  },
  {
    "path": "test/test_ejbca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for ejbca rest handler\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, Mock\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ejbca_ca_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.ca_handler.ejbca_ca_handler import CAhandler\n\n        self.cahandler = CAhandler(False, self.logger)\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"foo\"] = {\"foo\": \"bar\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_003__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        config_dic = {\"foo\": \"bar\"}\n        self.cahandler._config_server_load(config_dic)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_004__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_005__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_host\": \"api_host\"}\n        self.cahandler._config_server_load(parser)\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_006__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 10}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_007__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"20\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_008__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_server_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load request_timeout parameter:invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n\n    def test_009__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n\n    def test_010__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": False}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.ca_bundle)\n\n    def test_011__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n\n    def test_012__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username\": \"username\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertEqual(\"username\", self.cahandler.username)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n\n    def test_013__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_code\": \"enrollment_code\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertEqual(\"enrollment_code\", self.cahandler.enrollment_code)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n\n    def test_014__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username_append_cn\": True}\n        self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.session)\n        self.assertTrue(self.cahandler.username_append_cn)\n\n    def test_015__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username_append_cn\": False}\n        self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n\n    def test_016__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username_append_cn\": \"aa\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load username_append_cn parameter, using default value: False\",\n            lcm.output,\n        )\n\n    def test_017__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase\": \"cert_passphrase\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n\n    def test_018__config_auth_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_file\": \"cert_file\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertFalse(self.cahandler.session)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.requests.Session\")\n    def test_019__config_auth_load(self, mock_sess):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_file\": \"cert_file\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        mock_sess.return_value = Mock()\n        mock_sess.return_value.__enter__ = Mock()\n        mock_sess.return_value.__exit__ = Mock()\n        self.cahandler._config_auth_load(parser)\n        self.assertFalse(self.cahandler.username_append_cn)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertTrue(self.cahandler.session)\n\n    def test_020__config_cainfo_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler._config_cainfo_load(parser)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertFalse(self.cahandler.ee_profile_name)\n\n    def test_021__config_cainfo_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_name\": \"ca_name\"}\n        self.cahandler._config_cainfo_load(parser)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertFalse(self.cahandler.ee_profile_name)\n\n    def test_022__config_cainfo_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_profile_name\": \"cert_profile_name\"}\n        self.cahandler._config_cainfo_load(parser)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(\"cert_profile_name\", self.cahandler.cert_profile_name)\n        self.assertFalse(self.cahandler.ee_profile_name)\n\n    def test_023__config_cainfo_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ee_profile_name\": \"ee_profile_name\"}\n        self.cahandler._config_cainfo_load(parser)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertEqual(\"ee_profile_name\", self.cahandler.ee_profile_name)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_024_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"api_host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ee_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ca_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"enrollment_code\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_025_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.api_host = \"api_host\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ee_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ca_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"enrollment_code\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_026_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.cert_profile_name = \"cert_profile_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"api_host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ee_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ca_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"enrollment_code\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_027_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.ee_profile_name = \"ee_profile_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"api_host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ca_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"enrollment_code\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_028_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.ca_name = \"ca_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"api_host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ee_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"enrollment_code\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_029_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.username = \"username\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_auth_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_cainfo_load\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.load_config\")\n    def test_030_config_load(\n        self, mock_load_cfg, mock_cainfo, mock_auth_load, mock_server_load\n    ):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.enrollment_code = \"enrollment_code\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_cainfo.called)\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"api_host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ee_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"ca_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"username\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"username_var\": \"user_var\"})\n    def test_031_config_authuser_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username_variable\": \"username_var\"}\n        self.cahandler._config_authuser_load(parser)\n        self.assertEqual(\"user_var\", self.cahandler.username)\n\n    @patch.dict(\"os.environ\", {\"username_var\": \"user_var\"})\n    def test_032_config_authuser_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_authuser_load(parser)\n        self.assertFalse(self.cahandler.username)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load username_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"username_var\": \"user_var\"})\n    def test_033_config_authuser_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"username_variable\": \"username_var\",\n            \"username\": \"username\",\n        }\n        self.cahandler._config_authuser_load(parser)\n        self.assertEqual(\"username\", self.cahandler.username)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"bar\"})\n    def test_034_config_authuser_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\", \"foo1\": \"bar1\"}\n        self.cahandler._config_authuser_load(parser)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertFalse(self.cahandler.username)\n        # self.assertIn(\"foo\", lcm.output)\n\n    @patch.dict(\"os.environ\", {\"enrollment_code_var\": \"user_var\"})\n    def test_035_config_enrollmentcode_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_code_variable\": \"enrollment_code_var\"}\n        self.cahandler._config_enrollmentcode_load(parser)\n        self.assertEqual(\"user_var\", self.cahandler.enrollment_code)\n\n    @patch.dict(\"os.environ\", {\"enrollment_code_var\": \"user_var\"})\n    def test_036_config_enrollmentcode_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_code_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_enrollmentcode_load(parser)\n        self.assertFalse(self.cahandler.enrollment_code)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load enrollment_code_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"enrollment_code_var\": \"user_var\"})\n    def test_037_config_enrollmentcode_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"enrollment_code_variable\": \"enrollment_code_var\",\n            \"enrollment_code\": \"enrollment_code\",\n        }\n        self.cahandler._config_enrollmentcode_load(parser)\n        self.assertEqual(\"enrollment_code\", self.cahandler.enrollment_code)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"bar\"})\n    def test_038_config_enrollmentcode_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\", \"foo1\": \"bar1\"}\n        self.cahandler._config_enrollmentcode_load(parser)\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertFalse(self.cahandler.enrollment_code)\n        # self.assertIn(\"foo\", lcm.output)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_039_config_session_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_var\"}\n        self.cahandler._config_session_load(parser)\n        self.assertEqual(\"user_var\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_040_config_session_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertFalse(self.cahandler.cert_passphrase)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cert_passphrase_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_041_config_session_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_passphrase_variable\": \"cert_passphrase_var\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertIn(\n            \"INFO:test_a2c:CAhandler._config_load() overwrite cert_passphrase\",\n            lcm.output,\n        )\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"bar\"})\n    def test_042_config_session_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\", \"foo1\": \"bar1\"}\n        self.cahandler._config_session_load(parser)\n        self.assertFalse(self.cahandler.cert_passphrase)\n\n    @patch(\"requests.Session\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.Pkcs12Adapter\")\n    def test_043_config_session_load(self, mock_pkcs12, mock_session):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_file\": \"cert_file\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        mock_session.return_value.__enter__.return_value = Mock()\n        self.cahandler._config_session_load(parser)\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n        self.assertTrue(mock_pkcs12.called)\n        self.assertTrue(mock_session.called)\n\n    @patch(\"requests.Session\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.Pkcs12Adapter\")\n    def test_044_config_session_load(self, mock_pkcs12, mock_session):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase\": \"cert_passphrase\"}\n        mock_session.return_value.__enter__.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"cert_file\"/\"cert_passphrase\" parameter is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n        self.assertFalse(mock_pkcs12.called)\n        self.assertFalse(mock_session.called)\n\n    def test_045__api_post(self):\n        \"\"\"test _api_post successful run\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._api_post(\"url\", \"data\"))\n\n    def test_046__api_post(self):\n        \"\"\"CAhandler._api_post() returns an http error\"\"\"\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [Exception(\"exc_api_post\")]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"exc_api_post\", self.cahandler._api_post(\"url\", \"data\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API post() returned error: exc_api_post\",\n            lcm.output,\n        )\n\n    def test_047__api_put(self):\n        \"\"\"test _api_put successful run\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.put.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._api_put(\"url\"))\n\n    def test_048__api_put(self):\n        \"\"\"CAhandler._api_put() returns an http error\"\"\"\n        mockresponse = Mock()\n        mockresponse.put.side_effect = [Exception(\"exc_api_put\")]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"exc_api_put\", self.cahandler._api_put(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API put() returned error: exc_api_put\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_load\")\n    def test_049__enter(self, mock_cfgload):\n        \"\"\"CAhandler._enter() with config load\"\"\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfgload.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._config_load\")\n    def test_050__enter(self, mock_cfgload):\n        \"\"\"CAhandler._enter() with config load\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfgload.called)\n\n    def test_051__cert_status_check(self):\n        \"\"\"test _cert_status_check  successful run\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.cahandler.session = Mock()\n        self.cahandler.session.get.return_value = mockresponse\n        self.cahandler.api_host = \"api_host\"\n        self.assertEqual(\n            {\"foo\": \"bar\"},\n            self.cahandler._cert_status_check(\"issuer_dn\", \"cert_serial\"),\n        )\n\n    def test_052__cert_status_check(self):\n        \"\"\"test _cert_status_check no api host\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.cahandler.session = Mock()\n        self.cahandler.session.get.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._cert_status_check(\"issuer_dn\", \"cert_serial\")\n        self.assertIn(\n            \"ERROR:test_a2c:api_host parameter is missing in configuration\",\n            lcm.output,\n        )\n\n    def test_053__cert_status_check(self):\n        \"\"\"test _cert_status_check exception\"\"\"\n        mockresponse = Mock()\n        mockresponse.get.side_effect = [Exception(\"exc_cert_chk\")]\n        self.cahandler.session = mockresponse\n        self.cahandler.api_host = \"api_host\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                {\"error\": \"exc_cert_chk\", \"status\": \"nok\"},\n                self.cahandler._cert_status_check(\"issuer_dn\", \"cert_serial\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate status check returned error: exc_cert_chk\",\n            lcm.output,\n        )\n\n    def test_054__status_get(self):\n        \"\"\"test _status_get  successful run\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.cahandler.session = Mock()\n        self.cahandler.session.get.return_value = mockresponse\n        self.cahandler.api_host = \"api_host\"\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._status_get())\n\n    def test_055__status_get(self):\n        \"\"\"test _status_get  no api host\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.cahandler.session = Mock()\n        self.cahandler.session.get.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._status_get()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete: api_host parameter is missing in configuration\",\n            lcm.output,\n        )\n\n    def test_056__status_get(self):\n        \"\"\"test _cert_status_check exception\"\"\"\n        mockresponse = Mock()\n        mockresponse.get.side_effect = [Exception(\"exc_status_chk\")]\n        self.cahandler.session = mockresponse\n        self.cahandler.api_host = \"api_host\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                {\"error\": \"exc_status_chk\", \"status\": \"nok\"},\n                self.cahandler._status_get(),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get certificate status. Error: exc_status_chk\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_post\")\n    def test_057__sign(self, mock_post, mock_cn):\n        \"\"\"test _sign\"\"\"\n        self.cahandler.api_host = \"foo\"\n        mock_post.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.cahandler._sign(\"csr\"))\n        self.assertFalse(mock_cn.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._csr_cn_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_post\")\n    def test_058__sign(self, mock_post, mock_cn):\n        \"\"\"test _sign\"\"\"\n        self.cahandler.api_host = \"foo\"\n        mock_post.return_value = \"foo\"\n        self.cahandler.username_append_cn = True\n        self.assertEqual(\"foo\", self.cahandler._sign(\"csr\"))\n        self.assertTrue(mock_cn.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_post\")\n    def test_059__sign(self, mock_post):\n        \"\"\"test _sign\"\"\"\n        mock_post.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._sign(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete: api_host is missing in configuration\",\n            lcm.output,\n        )\n\n    def test_060_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_061_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_062_enroll(self, mock_status):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Unknown error\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment failed: Unknown error\", lcm.output)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_063_enroll(self, mock_status):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"nok\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Unknown error\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment failed: Unknown error\", lcm.output)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_064_enroll(self, mock_status):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"nok\", \"error\": \"error_msg\"}\n        self.assertEqual((\"error_msg\", None, None, None), self.cahandler.enroll(\"csr\"))\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_065_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n    ):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error. Malformed Rest response: {}\", lcm.output\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertFalse(mock_decode.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_b2s.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_066_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n    ):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\"certificate\": \"certificate\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error. Malformed Rest response: {'certificate': 'certificate'}\",\n            lcm.output,\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertFalse(mock_decode.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_b2s.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_067_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n        mock_ecl,\n    ):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\"certificate_chain\": \"certificate_chain\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error. Malformed Rest response: {'certificate_chain': 'certificate_chain'}\",\n            lcm.output,\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertFalse(mock_decode.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_068_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n        mock_ecl,\n    ):\n        \"\"\"test enrollment\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\"certificate_chain\": \"certificate_chain\"}\n        self.cahandler.enrollment_config_log = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error. Malformed Rest response: {'certificate_chain': 'certificate_chain'}\",\n            lcm.output,\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertFalse(mock_decode.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_069_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n        profile_header_info_check,\n    ):\n        \"\"\"test enrollment one ca-cert\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\n            \"certificate\": \"certificate\",\n            \"certificate_chain\": [\"certificate_chain\"],\n        }\n        mock_b2s.side_effect = [\n            \"foo1\",\n            \"foo2\",\n        ]\n        profile_header_info_check.return_value = False\n        self.assertEqual(\n            (None, \"foo1foo2\", \"certificate\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertTrue(mock_decode.called)\n        self.assertTrue(mock_d2p.called)\n        self.assertTrue(mock_b2s.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_070_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n        profile_header_info_check,\n    ):\n        \"\"\"test enrollment one ca-cert\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\n            \"certificate\": \"certificate\",\n            \"certificate_chain\": [\"certificate_chain\"],\n        }\n        mock_b2s.side_effect = [\n            \"foo1\",\n            \"foo2\",\n        ]\n        profile_header_info_check.return_value = \"error\"\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertFalse(mock_sign.called)\n        self.assertFalse(mock_decode.called)\n        self.assertFalse(mock_d2p.called)\n        self.assertFalse(mock_b2s.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_der2pem\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._sign\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._status_get\")\n    def test_071_enroll(\n        self,\n        mock_status,\n        mock_sign,\n        mock_decode,\n        mock_d2p,\n        mock_b2s,\n    ):\n        \"\"\"test enrollment two ca-certs\"\"\"\n        mock_status.return_value = {\"status\": \"ok\"}\n        mock_sign.return_value = {\n            \"certificate\": \"certificate\",\n            \"certificate_chain\": [\"certificate_chain\", \"certificate_chain\"],\n        }\n        mock_b2s.side_effect = [\"foo1\", \"foo2\", \"foo3\"]\n        self.assertEqual(\n            (None, \"foo1foo2foo3\", \"certificate\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_sign.called)\n        self.assertTrue(mock_decode.called)\n        self.assertTrue(mock_d2p.called)\n        self.assertTrue(mock_b2s.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_072_revoke(\n        self, mock_serial, mock_issuer, mock_status, mock_encode, mock_put\n    ):\n        \"\"\"test revoke operation malformed api response\"\"\"\n        mock_status.return_value = {}\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"Unknown status\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_073_revoke(\n        self, mock_serial, mock_issuer, mock_status, mock_encode, mock_put\n    ):\n        \"\"\"test revoke operation cert already revoked\"\"\"\n        mock_status.return_value = {\"revoked\": True}\n        self.assertEqual(\n            (\n                400,\n                \"urn:ietf:params:acme:error:alreadyRevoked\",\n                \"Certificate has already been revoked\",\n            ),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_074_revoke(\n        self, mock_serial, mock_issuer, mock_status, mock_encode, mock_put\n    ):\n        \"\"\"test revoke operation - revocation response malformed\"\"\"\n        mock_status.return_value = {\"revoked\": False}\n        mock_put.return_value = {\"foo\": \"bar\"}\n        self.cahandler.api_host = \"api_host\"\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"{'foo': 'bar'}\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_put.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_075_revoke(\n        self, mock_serial, mock_issuer, mock_status, mock_encode, mock_put\n    ):\n        \"\"\"test revoke operation - revocation unsuccessful\"\"\"\n        mock_status.return_value = {\"revoked\": False}\n        mock_put.return_value = {\"revoked\": False}\n        self.cahandler.api_host = \"api_host\"\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"{'revoked': False}\"),\n            self.cahandler.revoke(\"cert\"),\n        )\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_put.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_076_revoke(\n        self,\n        mock_serial,\n        mock_issuer,\n        mock_status,\n        mock_encode,\n        mock_put,\n        mock_revcheck,\n    ):\n        \"\"\"test revoke operation - revocation successful\"\"\"\n        mock_status.return_value = {\"revoked\": False}\n        mock_put.return_value = {\"revoked\": True}\n        self.cahandler.api_host = \"api_host\"\n        self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_put.called)\n        self.assertFalse(mock_revcheck.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._api_put\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.encode_url\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.CAhandler._cert_status_check\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_issuer_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.cert_serial_get\")\n    def test_077_revoke(\n        self,\n        mock_serial,\n        mock_issuer,\n        mock_status,\n        mock_encode,\n        mock_put,\n        mock_revcheck,\n    ):\n        \"\"\"test revoke operation - revocation successful\"\"\"\n        mock_status.return_value = {\"revoked\": False}\n        mock_put.return_value = {\"revoked\": True}\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.eab_profiling = True\n        self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_issuer.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_put.called)\n        self.assertTrue(mock_revcheck.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_cn_get\")\n    def test_078__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_san.return_value = [\"san0\", \"san1\"]\n        self.assertEqual(\"cn\", self.cahandler._csr_cn_get(\"csr\"))\n        self.assertFalse(mock_san.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_cn_get\")\n    def test_079__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"dns:san0\", \"dns:san1\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"san0\", self.cahandler._csr_cn_get(\"csr\"))\n        self.assertIn(\"INFO:test_a2c:CN not found in CSR\", lcm.output)\n        self.assertIn(\n            \"INFO:test_a2c:CN not found in CSR. Using first SAN entry as CN: san0\",\n            lcm.output,\n        )\n        self.assertTrue(mock_san.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.ejbca_ca_handler.csr_cn_get\")\n    def test_080__csr_cn_get(self, mock_cn, mock_san):\n        \"\"\"test _csr_cn_get()\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._csr_cn_get(\"csr\"))\n        self.assertIn(\"INFO:test_a2c:CN not found in CSR\", lcm.output)\n        self.assertIn(\n            \"ERROR:test_a2c:CN not found in CSR. No SAN entries found\",\n            lcm.output,\n        )\n        self.assertTrue(mock_san.called)\n\n    @patch(\"examples.ca_handler.ejbca_ca_handler.handler_config_check\")\n    def test_081_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    if os.path.exists(\"acme_test.db\"):\n        os.remove(\"acme_test.db\")\n    unittest.main()\n"
  },
  {
    "path": "test/test_email_handler.py",
    "content": "\"\"\"Test cases for EmailHandler class\"\"\"\nimport unittest\nfrom unittest.mock import MagicMock, Mock, patch, call\nimport threading\nimport logging\nimport time\nimport sys\nimport configparser\nfrom email.mime.base import MIMEBase\nfrom email.mime.text import MIMEText\nfrom email.mime.multipart import MIMEMultipart\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\nfrom acme_srv.email_handler import EmailHandler\n\n\nclass TestEmailHandler(unittest.TestCase):\n    \"\"\"Test EmailHandler class\"\"\"\n\n    def setUp(self):\n        \"\"\"Set up test fixtures\"\"\"\n        logging.basicConfig(level=logging.INFO)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.email_handler = EmailHandler(debug=True, logger=self.logger)\n\n    def _mock_mail(\n        self, search_status=\"OK\", search_ids=b\"1 2\", fetch_status=\"OK\", subjects=None\n    ):\n        \"\"\"Helper to create a mock mail object.\"\"\"\n        mail = MagicMock()\n        mail.search.return_value = (search_status, [search_ids])\n        # Prepare different subjects for each fetch\n        if subjects is None:\n            subjects = [\"Test 1\", \"Test 2\"]\n\n        def fetch_side_effect(email_id, _):\n            idx = int(email_id.decode()) - 1\n            msg = Mock()\n            msg.get.side_effect = lambda k, d=\"\": {\n                \"Subject\": subjects[idx],\n                \"From\": \"sender@test.com\",\n                \"To\": \"rcpt@test.com\",\n                \"Date\": \"2025-01-01\",\n            }.get(k, d)\n            msg.is_multipart.return_value = False\n            msg.get_payload.return_value = f\"Body {idx+1}\".encode()\n            return (fetch_status, [(None, msg.get_payload.return_value)])\n\n        mail.fetch.side_effect = fetch_side_effect\n        return mail\n\n    def tearDown(self):\n        \"\"\"Clean up after tests\"\"\"\n        if hasattr(self.email_handler, \"_polling_active\"):\n            self.email_handler.stop_polling()\n\n    @patch(\"acme_srv.email_handler.load_config\")\n    def test_001_config_load_default_section_exists(self, mock_load_config):\n        \"\"\"Test _config_load with DEFAULT section\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\n            \"imap_server\": \"imap.test.com\",\n            \"imap_port\": \"993\",\n            \"imap_use_ssl\": \"True\",\n            \"smtp_server\": \"smtp.test.com\",\n            \"smtp_port\": \"587\",\n            \"smtp_use_tls\": \"True\",\n            \"username\": \"test@test.com\",\n            \"password\": \"testpass\",\n            \"email_address\": \"test@test.com\",\n            \"polling_timer\": \"120\",\n            \"connection_timeout\": \"45\",\n        }\n\n        mock_load_config.return_value = parser\n        self.email_handler._config_load()\n\n        self.assertEqual(self.email_handler.imap_server, \"imap.test.com\")\n        self.assertEqual(self.email_handler.imap_port, 993)\n        self.assertTrue(self.email_handler.imap_use_ssl)\n        self.assertEqual(self.email_handler.smtp_server, \"smtp.test.com\")\n        self.assertEqual(self.email_handler.smtp_port, 587)\n        self.assertTrue(self.email_handler.smtp_use_tls)\n        self.assertEqual(self.email_handler.username, \"test@test.com\")\n        self.assertEqual(self.email_handler.password, \"testpass\")\n        self.assertEqual(self.email_handler.email_address, \"test@test.com\")\n        self.assertEqual(self.email_handler.polling_timer, 120)\n        self.assertEqual(self.email_handler.connection_timeout, 45)\n\n    @patch(\"acme_srv.email_handler.load_config\")\n    def test_002_config_load_fallback_values(self, mock_load_config):\n        \"\"\"Test _config_load with fallback values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\n            \"imap_server\": \"imap.test.com\",\n            \"username\": \"test@test.com\",\n            \"password\": \"testpass\",\n        }\n        mock_load_config.return_value = parser\n\n        self.email_handler._config_load()\n\n        self.assertEqual(\n            self.email_handler.smtp_server, \"imap.test.com\"\n        )  # fallback to imap_server\n        self.assertEqual(\n            self.email_handler.email_address, \"test@test.com\"\n        )  # fallback to username\n        self.assertEqual(self.email_handler.imap_port, 993)  # default\n        self.assertEqual(self.email_handler.smtp_port, 587)  # default\n        self.assertEqual(self.email_handler.polling_timer, 60)  # default\n        self.assertEqual(self.email_handler.connection_timeout, 30)  # default\n\n    @patch(\"acme_srv.email_handler.load_config\")\n    def test_003_config_load_no_default_section(self, mock_load_config):\n        \"\"\"Test _config_load without DEFAULT section\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_config.return_value = {}\n        with self.assertLogs(self.logger, level=\"WARNING\") as log:\n            self.email_handler._config_load()\n\n        self.assertIn(\n            \"WARNING:test_a2c:DEFAULT configuration section not found\", log.output\n        )\n\n    @patch(\"acme_srv.email_handler.load_config\")\n    def test_004_config_load_invalid_port_values(self, mock_load_config):\n        \"\"\"Test _config_load with invalid port values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\n            \"imap_port\": \"invalid\",\n            \"smtp_port\": \"invalid\",\n            \"polling_timer\": \"invalid\",\n            \"connection_timeout\": \"invalid\",\n        }\n\n        mock_load_config.return_value = parser\n        with self.assertLogs(self.logger, level=\"WARNING\") as log:\n            self.email_handler._config_load()\n\n        # Check warning messages\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse imap_port from configuration. Using default 993. Error: invalid literal for int() with base 10: 'invalid'\",\n            log.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse smtp_port from configuration. Using default 587. Error: invalid literal for int() with base 10: 'invalid'\",\n            log.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse polling_timer from configuration. Using default 60. Error: invalid literal for int() with base 10: 'invalid'\",\n            log.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse connection_timeout from configuration. Using default 30. Error: invalid literal for int() with base 10: 'invalid'\",\n            log.output,\n        )\n\n    def test_005_smtp_config_validate_valid(self):\n        \"\"\"Test _smtp_config_validate with valid configuration\"\"\"\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        result = self.email_handler._smtp_config_validate()\n        self.assertTrue(result)\n\n    def test_006_smtp_config_validate_missing_server(self):\n        \"\"\"Test _smtp_config_validate with missing server\"\"\"\n        self.email_handler.smtp_server = None\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            result = self.email_handler._smtp_config_validate()\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:SMTP server not configured\", log.output)\n\n    def test_007_smtp_config_validate_missing_email(self):\n        \"\"\"Test _smtp_config_validate with missing email\"\"\"\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.email_address = None\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            result = self.email_handler._smtp_config_validate()\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:Email address not configured\", log.output)\n\n    def test_008_smtp_config_validate_missing_credentials(self):\n        \"\"\"Test _smtp_config_validate with missing credentials\"\"\"\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = None\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            result = self.email_handler._smtp_config_validate()\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:Username or password not configured\", log.output)\n\n    def test_009_imap_config_validate_valid(self):\n        \"\"\"Test _imap_config_validate with valid configuration\"\"\"\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        result = self.email_handler._imap_config_validate()\n        self.assertTrue(result)\n\n    def test_010_imap_config_validate_missing_server(self):\n        \"\"\"Test _imap_config_validate with missing server\"\"\"\n        self.email_handler.imap_server = None\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            result = self.email_handler._imap_config_validate()\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:IMAP server not configured\", log.output)\n\n    def test_011_imap_config_validate_missing_credentials(self):\n        \"\"\"Test _imap_config_validate with missing credentials\"\"\"\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.username = None\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            result = self.email_handler._imap_config_validate()\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:Username or password not configured\", log.output)\n\n    @patch(\"acme_srv.email_handler.smtplib.SMTP\")\n    def test_012_send_email_success_tls(self, mock_smtp):\n        \"\"\"Test successful email sending with TLS\"\"\"\n        # Setup\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.smtp_port = 587\n        self.email_handler.smtp_use_tls = True\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n        self.email_handler.connection_timeout = 30\n\n        mock_server = MagicMock()\n        mock_smtp.return_value = mock_server\n\n        with self.assertLogs(self.logger, level=\"INFO\") as log:\n            # Test\n            result = self.email_handler.send(\n                to_address=\"recipient@test.com\",\n                subject=\"Test Subject\",\n                message=\"Test Message\",\n            )\n\n        # Assertions\n        self.assertTrue(result)\n        # Assertions\n        self.assertTrue(result)\n        mock_smtp.assert_called_once_with(\"smtp.test.com\", 587, timeout=30)\n        mock_server.starttls.assert_called_once()\n        mock_server.login.assert_called_once_with(\"test@test.com\", \"testpass\")\n        mock_server.send_message.assert_called_once()\n        mock_server.quit.assert_called_once()\n        self.assertIn(\n            \"INFO:test_a2c:Email sent successfully to recipient@test.com\", log.output\n        )\n\n    @patch(\"acme_srv.email_handler.smtplib.SMTP_SSL\")\n    def test_013_send_email_success_ssl(self, mock_smtp_ssl):\n        \"\"\"Test successful email sending with SSL\"\"\"\n        # Setup\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.smtp_port = 465\n        self.email_handler.smtp_use_tls = False\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_server = MagicMock()\n        mock_smtp_ssl.return_value = mock_server\n\n        # Test\n        result = self.email_handler.send(\n            to_address=\"recipient@test.com\",\n            subject=\"Test Subject\",\n            message=\"Test Message\",\n        )\n\n        # Assertions\n        self.assertTrue(result)\n        mock_smtp_ssl.assert_called_once()\n        mock_server.starttls.assert_not_called()\n        mock_server.send_message.assert_called_once()\n\n    @patch(\"acme_srv.email_handler.smtplib.SMTP\")\n    def test_014_send_email_with_html(self, mock_smtp):\n        \"\"\"Test sending email with HTML content\"\"\"\n        # Setup\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_server = MagicMock()\n        mock_smtp.return_value = mock_server\n\n        # Test\n        result = self.email_handler.send(\n            to_address=\"recipient@test.com\",\n            subject=\"Test Subject\",\n            message=\"Test Message\",\n            html_message=\"<html><body>Test HTML</body></html>\",\n        )\n\n        # Assertions\n        self.assertTrue(result)\n        mock_server.send_message.assert_called_once()\n\n    def test_015_send_email_invalid_config(self):\n        \"\"\"Test send email with invalid configuration\"\"\"\n        result = self.email_handler.send(\n            to_address=\"recipient@test.com\",\n            subject=\"Test Subject\",\n            message=\"Test Message\",\n        )\n        self.assertFalse(result)\n\n    @patch(\"acme_srv.email_handler.smtplib.SMTP\")\n    def test_016_send_email_exception(self, mock_smtp):\n        \"\"\"Test send email with exception\"\"\"\n        # Setup\n        self.email_handler.smtp_server = \"smtp.test.com\"\n        self.email_handler.email_address = \"test@test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_smtp.side_effect = Exception(\"SMTP Error\")\n\n        # Test\n        result = self.email_handler.send(\n            to_address=\"recipient@test.com\",\n            subject=\"Test Subject\",\n            message=\"Test Message\",\n        )\n\n        # Assertions\n        self.assertFalse(result)\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            self.logger.error(\"Failed to send email: %s\", \"SMTP Error\")\n\n    @patch(\"acme_srv.email_handler.imaplib.IMAP4_SSL\")\n    def test_017_receive_emails_success(self, mock_imap):\n        \"\"\"Test successful email receiving\"\"\"\n        # Setup\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.imap_port = 993\n        self.email_handler.imap_use_ssl = True\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_mail = MagicMock()\n        mock_imap.return_value = mock_mail\n        mock_mail.search.return_value = (\"OK\", [b\"1 2\"])\n\n        # Mock email message\n        mock_email_data = b\"Subject: Test\\r\\nFrom: sender@test.com\\r\\n\\r\\nTest body\"\n        mock_mail.fetch.return_value = (\"OK\", [(None, mock_email_data)])\n\n        # Test\n        emails = self.email_handler.receive()\n\n        # Assertions\n        self.assertEqual(len(emails), 2)  # Two email IDs: 1 and 2\n        mock_mail.login.assert_called_once_with(\"test@test.com\", \"testpass\")\n        mock_mail.select.assert_called_once_with(\"INBOX\")\n        mock_mail.search.assert_called_once_with(None, \"UNSEEN\")\n\n    @patch(\"acme_srv.email_handler.imaplib.IMAP4\")\n    def test_018_receive_emails_no_ssl(self, mock_imap):\n        \"\"\"Test email receiving without SSL\"\"\"\n        # Setup\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.imap_use_ssl = False\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_mail = MagicMock()\n        mock_imap.return_value = mock_mail\n        mock_mail.search.return_value = (\"OK\", [b\"\"])\n\n        # Test\n        emails = self.email_handler.receive()\n\n        # Assertions\n        mock_imap.assert_called_once()\n        self.assertEqual(len(emails), 0)\n\n    def test_019_receive_emails_invalid_config(self):\n        \"\"\"Test receive emails with invalid configuration\"\"\"\n        emails = self.email_handler.receive()\n        self.assertEqual(len(emails), 0)\n\n    @patch(\"acme_srv.email_handler.imaplib.IMAP4_SSL\")\n    def test_020_receive_emails_exception(self, mock_imap):\n        \"\"\"Test receive emails with exception\"\"\"\n        # Setup\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n\n        mock_imap.side_effect = Exception(\"IMAP Error\")\n\n        # Test\n        emails = self.email_handler.receive()\n\n        # Assertions\n        self.assertEqual(len(emails), 0)\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            self.logger.error(\"Failed to receive emails: %s\", \"IMAP Error\")\n\n    def test_021_email_parse_simple(self):\n        \"\"\"Test parsing simple email\"\"\"\n        # Create mock email message\n        mock_msg = MagicMock()\n        mock_msg.get.side_effect = lambda key, default=\"\": {\n            \"Subject\": \"Test Subject\",\n            \"From\": \"sender@test.com\",\n            \"To\": \"recipient@test.com\",\n            \"Date\": \"Wed, 01 Jan 2025 12:00:00 +0000\",\n        }.get(key, default)\n        mock_msg.is_multipart.return_value = False\n        mock_msg.get_payload.return_value = b\"Test message body\"\n\n        # Test\n        parsed = self.email_handler._email_parse(mock_msg)\n\n        # Assertions\n        self.assertEqual(parsed[\"subject\"], \"Test Subject\")\n        self.assertEqual(parsed[\"from\"], \"sender@test.com\")\n        self.assertEqual(parsed[\"to\"], \"recipient@test.com\")\n        self.assertEqual(parsed[\"body\"], \"Test message body\")\n        self.assertEqual(parsed[\"html_body\"], \"\")\n        self.assertEqual(len(parsed[\"attachments\"]), 0)\n\n    def test_022_email_parse_multipart_text_html(self):\n        \"\"\"Test parsing multipart email with text and HTML\"\"\"\n        # Create multipart email\n        msg = MIMEMultipart(\"alternative\")\n        msg[\"Subject\"] = \"Multipart Test\"\n        msg[\"From\"] = \"sender@example.com\"\n        msg[\"To\"] = \"recipient@example.com\"\n        msg[\"Date\"] = \"Thu, 02 Jan 2025 10:30:00 +0000\"\n\n        # Add text part\n        text_part = MIMEText(\"This is the plain text version\", \"plain\")\n        msg.attach(text_part)\n\n        # Add HTML part\n        html_part = MIMEText(\n            \"<html><body><h1>This is the HTML version</h1></body></html>\", \"html\"\n        )\n        msg.attach(html_part)\n\n        # Parse the email\n        parsed = self.email_handler._email_parse(msg)\n\n        # Assertions\n        self.assertEqual(parsed[\"subject\"], \"Multipart Test\")\n        self.assertEqual(parsed[\"from\"], \"sender@example.com\")\n        self.assertEqual(parsed[\"to\"], \"recipient@example.com\")\n        self.assertEqual(parsed[\"date\"], \"Thu, 02 Jan 2025 10:30:00 +0000\")\n        self.assertEqual(parsed[\"body\"], \"This is the plain text version\")\n        self.assertEqual(\n            parsed[\"html_body\"],\n            \"<html><body><h1>This is the HTML version</h1></body></html>\",\n        )\n        self.assertEqual(len(parsed[\"attachments\"]), 0)\n\n    def test_023_email_parse_with_attachment(self):\n        \"\"\"Test parsing email with attachment\"\"\"\n        # Create multipart email with attachment\n        msg = MIMEMultipart()\n        msg[\"Subject\"] = \"Email with Attachment\"\n        msg[\"From\"] = \"sender@example.com\"\n        msg[\"To\"] = \"recipient@example.com\"\n\n        # Add text part\n        text_part = MIMEText(\"Email with attachment\", \"plain\")\n        msg.attach(text_part)\n\n        # Add attachment\n        attachment = MIMEBase(\"application\", \"octet-stream\")\n        attachment.set_payload(b\"This is attachment content\")\n        attachment.add_header(\"Content-Disposition\", 'attachment; filename=\"test.txt\"')\n        msg.attach(attachment)\n\n        # Parse the email\n        parsed = self.email_handler._email_parse(msg)\n\n        # Assertions\n        self.assertEqual(parsed[\"subject\"], \"Email with Attachment\")\n        self.assertEqual(parsed[\"body\"], \"Email with attachment\")\n        self.assertEqual(len(parsed[\"attachments\"]), 1)\n        self.assertEqual(parsed[\"attachments\"][0][\"filename\"], \"test.txt\")\n        self.assertEqual(\n            parsed[\"attachments\"][0][\"content_type\"], \"application/octet-stream\"\n        )\n        self.assertEqual(\n            parsed[\"attachments\"][0][\"content\"], b\"This is attachment content\"\n        )\n\n    def test_024_start_polling(self):\n        \"\"\"Test starting email polling\"\"\"\n        callback = MagicMock()\n\n        with patch.object(self.email_handler, \"_polling_loop\") as mock_loop:\n            with self.assertLogs(self.logger, level=\"INFO\") as log:\n                self.email_handler.start_polling(callback)\n\n            self.assertTrue(self.email_handler._polling_active)\n            self.assertEqual(self.email_handler._email_callback, callback)\n            self.assertIsNotNone(self.email_handler._polling_thread)\n            self.assertIn(\n                \"INFO:test_a2c:Email polling started with 60 second interval\",\n                log.output,\n            )\n\n    def test_025_start_polling_already_active(self):\n        \"\"\"Test starting polling when already active\"\"\"\n        self.email_handler._polling_active = True\n        callback = MagicMock()\n\n        with self.assertLogs(self.logger, level=\"WARNING\") as log:\n            self.email_handler.start_polling(callback)\n        self.assertIn(\"WARNING:test_a2c:Email polling is already active\", log.output)\n\n    def test_026_stop_polling(self):\n        \"\"\"Test stopping email polling\"\"\"\n        # Setup active polling\n        self.email_handler._polling_active = True\n        mock_thread = MagicMock()\n        self.email_handler._polling_thread = mock_thread\n\n        with self.assertLogs(self.logger, level=\"INFO\") as log:\n            self.email_handler.stop_polling()\n\n        self.assertFalse(self.email_handler._polling_active)\n        mock_thread.join.assert_called_once_with(timeout=5)\n        self.assertIn(\"INFO:test_a2c:Email polling stopped\", log.output)\n\n    def test_027_stop_polling_not_active(self):\n        \"\"\"Test stopping polling when not active\"\"\"\n        self.email_handler._polling_active = False\n\n        self.email_handler.stop_polling()\n\n        # Should not log anything since polling wasn't active\n\n    @patch(\"acme_srv.email_handler.time.sleep\")\n    def test_028_polling_loop(self, mock_sleep):\n        \"\"\"Test polling loop functionality\"\"\"\n        # Setup\n        self.email_handler._polling_active = True\n        self.email_handler.polling_timer = 2\n        callback = MagicMock()\n        self.email_handler._email_callback = callback\n\n        # Mock receive method\n        with patch.object(self.email_handler, \"receive\") as mock_receive:\n            mock_receive.return_value = [{\"subject\": \"test\"}]\n\n            # Start polling loop in thread\n            thread = threading.Thread(\n                target=self.email_handler._polling_loop, args=(\"INBOX\", True)\n            )\n            thread.start()\n\n            # Let it run briefly then stop\n            time.sleep(0.1)\n            self.email_handler._polling_active = False\n            thread.join(timeout=1)\n\n            # Verify receive was called\n            mock_receive.assert_called()\n\n    @patch(\"acme_srv.email_handler.EmailHandler.receive\")\n    @patch(\"time.sleep\")\n    def test_029_polling_loop(self, mock_sleep, mock_receive):\n        \"\"\"Test polling loop functionality\"\"\"\n        mock_sleep.return_value = MagicMock()\n        self.email_handler._polling_active = True\n        self.email_handler.polling_timer = 2\n        callback = MagicMock()\n        self.email_handler._email_callback = callback\n\n        mock_receive.side_effect = Exception(\"Receive error\")\n\n        with self.assertLogs(self.logger, level=\"ERROR\") as log:\n            # Start polling loop in thread\n            thread = threading.Thread(\n                target=self.email_handler._polling_loop, args=(\"INBOX\", True)\n            )\n            thread.start()\n            time.sleep(0.01)\n            self.email_handler._polling_active = False\n            thread.join(timeout=1)\n        self.assertIn(\n            \"ERROR:test_a2c:Error during email polling: Receive error\", log.output\n        )\n\n    def test_030_context_manager(self):\n        \"\"\"Test context manager functionality\"\"\"\n        with patch.object(self.email_handler, \"_config_load\") as mock_config:\n            with patch.object(self.email_handler, \"stop_polling\") as mock_stop:\n                with self.email_handler as handler:\n                    self.assertEqual(handler, self.email_handler)\n                    mock_config.assert_called_once()\n                mock_stop.assert_called_once()\n\n    @patch(\"acme_srv.email_handler.imaplib.IMAP4_SSL\")\n    def test_031_receive_with_callback(self, mock_imap):\n        \"\"\"Test receive method with callback function\"\"\"\n        # Setup\n        self.email_handler.imap_server = \"imap.test.com\"\n        self.email_handler.imap_port = 993\n        self.email_handler.imap_use_ssl = True\n        self.email_handler.username = \"test@test.com\"\n        self.email_handler.password = \"testpass\"\n        self.email_handler.connection_timeout = 30\n\n        # Mock IMAP connection\n        mock_mail = MagicMock()\n        mock_imap.return_value = mock_mail\n        mock_mail.search.return_value = (\"OK\", [b\"1 2 3\"])  # Three email IDs\n\n        # Create mock email messages\n        email1_data = b\"Subject: Test Email 1\\r\\nFrom: sender1@test.com\\r\\nTo: recipient@test.com\\r\\n\\r\\nFirst email body\"\n        email2_data = b\"Subject: Test Email 2\\r\\nFrom: sender2@test.com\\r\\nTo: recipient@test.com\\r\\n\\r\\nSecond email body\"\n        email3_data = b\"Subject: Test Email 3\\r\\nFrom: sender3@test.com\\r\\nTo: recipient@test.com\\r\\n\\r\\nThird email body\"\n\n        # Mock fetch to return different emails for each call\n        mock_mail.fetch.side_effect = [\n            (\"OK\", [(None, email1_data)]),\n            (\"OK\", [(None, email2_data)]),\n            (\"OK\", [(None, email3_data)]),\n        ]\n\n        # Create callback function to track calls\n        callback_calls = []\n\n        def test_callback(email_data):\n            result = None\n            if email_data[\"subject\"] == \"Test Email 2\":\n                result = email_data\n            else:\n                callback_calls.append(email_data)\n            return result\n\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log:\n            # Test receive with callback\n            emails = self.email_handler.receive(callback=test_callback)\n\n        # Assertions\n        self.assertEqual(\"Test Email 2\", emails[\"subject\"])\n\n        # Verify IMAP operations\n        mock_mail.login.assert_called_once_with(\"test@test.com\", \"testpass\")\n        mock_mail.select.assert_called_once_with(\"INBOX\")\n        mock_mail.search.assert_called_once_with(None, \"UNSEEN\")\n        self.assertEqual(mock_mail.fetch.call_count, 2)\n\n        # Verify callback was called for each email\n        self.assertEqual(callback_calls[0][\"subject\"], \"Test Email 1\")\n        self.assertEqual(callback_calls[0][\"from\"], \"sender1@test.com\")\n\n        # Verify emails marked as read (default behavior)\n        # expected_store_calls = [\n        #    call(b\"1\", \"+FLAGS\", \"\\\\Seen\"),\n        #    call(b\"2\", \"+FLAGS\", \"\\\\Seen\"),\n        #    call(b\"3\", \"+FLAGS\", \"\\\\Seen\"),\n        # ]\n        # mock_mail.store.assert_has_calls(expected_store_calls)\n\n        # Verify connection cleanup\n        mock_mail.close.assert_called_once()\n        mock_mail.logout.assert_called_once()\n\n        self.assertIn(\n            \"DEBUG:test_a2c:EmailHandler.receive(): email did not pass filter: Test Email 1\",\n            log.output,\n        )\n        self.assertIn(\"INFO:test_a2c:Email passed filter: Test Email 2\", log.output)\n        # self.assertIn('DEBUG:test_a2c:mailHandler.receive(): email did not pass filter: Test Email 3', log.output)\n\n    def test_emails_fetch_no_emails(self):\n        mail = self._mock_mail(search_ids=b\"\")\n        emails = self.email_handler._emails_fetch(\n            mail, callback=None, mark_as_read=True\n        )\n        self.assertEqual(emails, [])\n\n    def test_emails_fetch_multiple_emails(self):\n        mail = self._mock_mail()\n        # Patch _email_parse to return a simple dict\n        self.email_handler._email_parse = lambda msg: {\n            \"subject\": \"Test\",\n            \"body\": \"Body\",\n        }\n        emails = self.email_handler._emails_fetch(\n            mail, callback=None, mark_as_read=True\n        )\n        self.assertEqual(len(emails), 2)\n        mail.store.assert_any_call(b\"1\", \"+FLAGS\", \"\\\\Seen\")\n        mail.store.assert_any_call(b\"2\", \"+FLAGS\", \"\\\\Seen\")\n\n    def test_emails_fetch_mark_as_unread(self):\n        mail = self._mock_mail()\n        self.email_handler._email_parse = lambda msg: {\n            \"subject\": \"Test\",\n            \"body\": \"Body\",\n        }\n        emails = self.email_handler._emails_fetch(\n            mail, callback=None, mark_as_read=False\n        )\n        mail.store.assert_any_call(b\"1\", \"-FLAGS\", \"\\\\Seen\")\n        mail.store.assert_any_call(b\"2\", \"-FLAGS\", \"\\\\Seen\")\n\n    def test_emails_fetch_with_callback_returns_result(self):\n        mail = self._mock_mail()\n        # Only return a result for the first email\n        def callback(email):\n            return email if email[\"subject\"] == \"Test\" else None\n\n        self.email_handler._email_parse = lambda msg: {\n            \"subject\": \"Test\",\n            \"body\": \"Body\",\n        }\n        emails = self.email_handler._emails_fetch(\n            mail, callback=callback, mark_as_read=True\n        )\n        self.assertEqual(emails, {\"subject\": \"Test\", \"body\": \"Body\"})\n\n    def test_emails_fetch_with_callback_filters_all(self):\n        mail = self._mock_mail(subjects=[\"NoMatch\", \"NoMatch2\"])\n\n        def callback(email):\n            return None  # Filter out all emails\n\n        self.email_handler._email_parse = lambda msg: {\n            \"subject\": msg.get(\"Subject\"),\n            \"body\": \"Body\",\n        }\n        emails = self.email_handler._emails_fetch(\n            mail, callback=callback, mark_as_read=True\n        )\n        self.assertEqual(emails, [])\n\n    def test_emails_fetch_search_not_ok(self):\n        mail = self._mock_mail(search_status=\"NO\")\n        emails = self.email_handler._emails_fetch(\n            mail, callback=None, mark_as_read=True\n        )\n        self.assertEqual(emails, [])\n\n    def test_emails_fetch_fetch_not_ok(self):\n        mail = self._mock_mail(fetch_status=\"NO\")\n        self.email_handler._email_parse = lambda msg: {\n            \"subject\": \"Test\",\n            \"body\": \"Body\",\n        }\n        emails = self.email_handler._emails_fetch(\n            mail, callback=None, mark_as_read=True\n        )\n        self.assertEqual(emails, [])\n\n    @patch.object(EmailHandler, \"send\")\n    def test_032_send_email_challenge_basic_functionality(self, mock_send):\n        \"\"\"Test send_email_challenge basic functionality\"\"\"\n        # Setup\n        to_address = \"test@example.com\"\n        token1 = \"abc123token\"\n\n        # Test\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log:\n            self.email_handler.send_email_challenge(\n                to_address=to_address, token1=token1\n            )\n\n        # Verify send was called with correct parameters\n        mock_send.assert_called_once()\n        call_args = mock_send.call_args\n\n        # Check arguments passed to send()\n        self.assertEqual(call_args[1][\"to_address\"], to_address)\n        self.assertEqual(call_args[1][\"subject\"], f\"ACME: {token1}\")\n\n        # Check message content\n        message = call_args[1][\"message\"]\n        self.assertIn(to_address, message)\n        self.assertIn(\"ACME challenge\", message)\n        self.assertIn(\"S/MIME certificate\", message)\n        self.assertIn(\"security precautions\", message)\n        self.assertIn(\"email client\", message)\n        self.assertIn(\"verification tool\", message)\n\n        # Verify debug logging\n        self.assertTrue(\n            any(\"Challenge._email_send\" in message for message in log.output)\n        )\n        self.assertTrue(any(to_address in message for message in log.output))\n\n    @patch.object(EmailHandler, \"send\")\n    def test_033_send_email_challenge_with_none_parameters(self, mock_send):\n        \"\"\"Test send_email_challenge with None parameters\"\"\"\n        # Test with None to_address\n        self.email_handler.send_email_challenge(to_address=None, token1=\"token123\")\n\n        call_args = mock_send.call_args\n        self.assertEqual(call_args[1][\"to_address\"], None)\n        self.assertEqual(call_args[1][\"subject\"], \"ACME: token123\")\n\n        # Reset mock\n        mock_send.reset_mock()\n\n        # Test with None token1\n        self.email_handler.send_email_challenge(\n            to_address=\"test@example.com\", token1=None\n        )\n\n        call_args = mock_send.call_args\n        self.assertEqual(call_args[1][\"to_address\"], \"test@example.com\")\n        self.assertEqual(call_args[1][\"subject\"], \"ACME: None\")\n\n        # Reset mock\n        mock_send.reset_mock()\n\n        # Test with both None\n        self.email_handler.send_email_challenge(to_address=None, token1=None)\n\n        call_args = mock_send.call_args\n        self.assertEqual(call_args[1][\"to_address\"], None)\n        self.assertEqual(call_args[1][\"subject\"], \"ACME: None\")\n\n    @patch.object(EmailHandler, \"send\")\n    def test_034_send_email_challenge_message_content(self, mock_send):\n        \"\"\"Test send_email_challenge message content formatting\"\"\"\n        to_address = \"user@domain.com\"\n        token1 = \"xyz789token\"\n\n        self.email_handler.send_email_challenge(to_address=to_address, token1=token1)\n\n        call_args = mock_send.call_args\n        message = call_args[1][\"message\"]\n\n        # Check specific message content (accounting for line breaks)\n        expected_parts = [\n            \"automatically generated ACME challenge\",\n            f'\"{to_address}\"',\n            \"S/MIME certificate\",\n            \"disregard this message\",\n            \"security precautions\",\n            \"email client may be able to process\",\n            \"challenge automatically\",\n            \"manually\",\n            \"copy the first token\",\n            \"designated verification tool\",\n            \"or application\",\n        ]\n\n        for part in expected_parts:\n            self.assertIn(part, message)\n\n    @patch.object(EmailHandler, \"send\")\n    def test_035_send_email_challenge_subject_formatting(self, mock_send):\n        \"\"\"Test send_email_challenge subject formatting\"\"\"\n        test_cases = [\n            (\"simple_token\", \"ACME: simple_token\"),\n            (\"\", \"ACME: \"),\n            (\"token-with-dashes\", \"ACME: token-with-dashes\"),\n            (\"token_with_underscores\", \"ACME: token_with_underscores\"),\n            (\"TokenWithMixedCase\", \"ACME: TokenWithMixedCase\"),\n            (\"token.with.dots\", \"ACME: token.with.dots\"),\n        ]\n\n        for token, expected_subject in test_cases:\n            mock_send.reset_mock()\n            self.email_handler.send_email_challenge(\n                to_address=\"test@example.com\", token1=token\n            )\n\n            call_args = mock_send.call_args\n            self.assertEqual(call_args[1][\"subject\"], expected_subject)\n\n    @patch.object(EmailHandler, \"send\")\n    def test_036_send_email_challenge_no_default_parameters(self, mock_send):\n        \"\"\"Test send_email_challenge called without any parameters\"\"\"\n        # This should work since both parameters have default None values\n        self.email_handler.send_email_challenge()\n\n        call_args = mock_send.call_args\n        self.assertEqual(call_args[1][\"to_address\"], None)\n        self.assertEqual(call_args[1][\"subject\"], \"ACME: None\")\n\n        message = call_args[1][\"message\"]\n        self.assertIn('\"None\"', message)  # None gets formatted into the message\n\n    @patch.object(EmailHandler, \"send\", return_value=True)\n    def test_037_send_email_challenge_integration_success(self, mock_send):\n        \"\"\"Test send_email_challenge integration when send succeeds\"\"\"\n        to_address = \"success@example.com\"\n        token1 = \"success_token\"\n\n        # The function doesn't return anything, but we can verify it calls send\n        result = self.email_handler.send_email_challenge(\n            to_address=to_address, token1=token1\n        )\n\n        # send_email_challenge doesn't return anything\n        self.assertIsNone(result)\n        mock_send.assert_called_once()\n\n    @patch.object(EmailHandler, \"send\", return_value=False)\n    def test_038_send_email_challenge_integration_failure(self, mock_send):\n        \"\"\"Test send_email_challenge integration when send fails\"\"\"\n        to_address = \"fail@example.com\"\n        token1 = \"fail_token\"\n\n        # The function doesn't return anything, even if send fails\n        result = self.email_handler.send_email_challenge(\n            to_address=to_address, token1=token1\n        )\n\n        # send_email_challenge doesn't return anything\n        self.assertIsNone(result)\n        mock_send.assert_called_once()\n\n    @patch.object(EmailHandler, \"send\", side_effect=Exception(\"SMTP error\"))\n    def test_039_send_email_challenge_send_exception(self, mock_send):\n        \"\"\"Test send_email_challenge when send raises an exception\"\"\"\n        to_address = \"error@example.com\"\n        token1 = \"error_token\"\n\n        # The exception should propagate since send_email_challenge doesn't handle it\n        with self.assertRaises(Exception) as context:\n            self.email_handler.send_email_challenge(\n                to_address=to_address, token1=token1\n            )\n\n        self.assertEqual(str(context.exception), \"SMTP error\")\n        mock_send.assert_called_once()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_email_hooks.py",
    "content": "import unittest\nfrom unittest.mock import patch, MagicMock, PropertyMock\nimport sys\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass DummyConfig:\n    \"\"\"Shim to emulate config parser used by Hooks\"\"\"\n\n    def __init__(self, data):\n        self._data = data\n\n    def __contains__(self, key):\n        return key in self._data\n\n    def __getitem__(self, key):\n        return self._data[key]\n\n    def get(self, section, key, fallback=None):\n        return self._data.get(section, {}).get(key, fallback)\n\n    def getint(self, section, key, fallback=None):\n        val = self.get(section, key, fallback)\n        try:\n            return int(val) if val is not None else fallback\n        except (TypeError, ValueError):\n            return fallback\n\n    def getboolean(self, section, key, fallback=None):\n        val = self.get(section, key, fallback)\n        if val is None:\n            return fallback\n        if isinstance(val, bool):\n            return val\n        return str(val).strip().lower() in (\"true\", \"1\", \"yes\", \"on\")\n\n\nclass TestHooks(unittest.TestCase):\n    \"\"\"Tests for email_hooks.Hooks\"\"\"\n\n    def setUp(self):\n        import logging\n\n        logging.basicConfig(level=logging.INFO)\n        self.logger = logging.getLogger(__name__)\n        # Start patching load_config before importing and instantiating Hooks\n        self._config_patch = patch(\n            \"examples.hooks.email_hooks.load_config\",\n            return_value=DummyConfig(\n                {\n                    \"Hooks\": {\n                        \"appname\": \"acme2certifier\",\n                        \"sender\": \"sender@example.com\",\n                        \"rcpt\": \"rcpt@example.com\",\n                    }\n                }\n            ),\n        )\n        self._config_patch_started = self._config_patch.start()\n        self.addCleanup(self._config_patch.stop)\n\n        from examples.hooks.email_hooks import Hooks\n\n        self.hooks = Hooks(self.logger)\n\n    def test_001_init(self):\n        \"\"\"test init\"\"\"\n        self.assertEqual(self.hooks.appname, \"acme2certifier\")\n        self.assertEqual(self.hooks.sender, \"sender@example.com\")\n        self.assertEqual(self.hooks.rcpt, \"rcpt@example.com\")\n\n    def test_002_validate_configuration_valid(self):\n        \"\"\"validate_configuration passes for complete config\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"acme2certifier\",\n                \"sender\": \"sender@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            self.assertEqual(h.appname, \"acme2certifier\")\n            self.assertEqual(h.sender, \"sender@example.com\")\n            self.assertEqual(h.rcpt, \"rcpt@example.com\")\n\n    def test_003_validate_configuration_empty_config(self):\n        \"\"\"validate_configuration raises on None/empty config\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        with patch(\"examples.hooks.email_hooks.load_config\", return_value=None):\n            with self.assertRaises(ValueError) as ctx:\n                Hooks(self.logger)\n            self.assertIn(\n                \"Configuration dictionary is empty or None\", str(ctx.exception)\n            )\n\n    def test_004_validate_configuration_missing_section(self):\n        \"\"\"Fails when both Hooks and DEFAULT sections are missing from configuration\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        config = DummyConfig({\"SomeOther\": {\"key\": \"value\"}})\n        with patch(\"examples.hooks.email_hooks.load_config\", return_value=config):\n            with self.assertRaises(ValueError) as ctx:\n                Hooks(self.logger)\n            self.assertIn(\n                \"Missing 'Hooks' or 'DEFAULT' section in configuration\",\n                str(ctx.exception),\n            )\n\n    def test_005_validate_configuration_missing_required_keys(self):\n        \"\"\"validate_configuration raises when required keys missing\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"foo\": \"acme2certifier\",\n                # missing sender and rcpt\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            with self.assertRaises(ValueError) as ctx:\n                Hooks(self.logger)\n            msg = str(ctx.exception)\n            self.assertIn(\"Missing required configuration key(s) in [Hooks]\", msg)\n\n    def test_006_validate_configuration_empty_required_keys(self):\n        \"\"\"Fails when required keys have empty values\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        config = DummyConfig(\n            {\n                \"Hooks\": {\n                    \"appname\": \"\",  # Empty required key\n                    \"sender\": \"\",  # Empty required key\n                    \"rcpt\": \"\",  # Empty required key\n                    \"smtp_server\": \"smtp.example.com\",\n                    \"smtp_port\": \"25\",\n                    \"smtp_user\": \"\",\n                    \"smtp_password\": \"\",\n                    \"smtp_timeout\": \"\",\n                    \"ssl_use\": \"\",\n                    \"ssl_starttls\": \"\",\n                    \"ssl_noverify\": \"\",\n                    \"subject\": \"ACME certificate renewal\",\n                    \"body\": \"Certificate for {subject} expires on {expires}.\",\n                    \"certificate_list\": \"/path/to/certs\",\n                }\n            }\n        )\n        with patch(\"examples.hooks.email_hooks.load_config\", return_value=config):\n            with self.assertRaises(ValueError) as ctx:\n                Hooks(self.logger)\n            msg = str(ctx.exception)\n            self.assertIn(\"Empty required configuration key(s): appname\", msg)\n\n    def test_007_smtp_valid_port_and_timeout(self):\n        \"\"\"Validates correct port and timeout do not log errors\"\"\"\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"smtp_port\": 587,\n                \"smtp_timeout\": 30,\n                \"smtp_username\": \"user\",\n                \"smtp_password\": \"pw\",\n            }\n        )\n        # Should not raise or log errors for valid config\n        self.hooks._validate_smtp_configuration()\n\n    def test_008_smtp_invalid_timeout(self):\n        \"\"\"Logs error for invalid smtp_timeout\"\"\"\n        self.hooks.config_dic[\"Hooks\"][\"smtp_timeout\"] = 9999\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(any(\"Invalid SMTP timeout\" in msg for msg in cm.output))\n\n    def test_009_smtp_password_no_username(self):\n        \"\"\"Logs debug when password is set but username is missing\"\"\"\n        self.hooks.config_dic[\"Hooks\"].pop(\"smtp_username\", None)\n        self.hooks.config_dic[\"Hooks\"][\"smtp_password\"] = \"pw\"\n        with self.assertLogs(self.logger, level=\"DEBUG\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(\n            any(\"SMTP password provided without username\" in msg for msg in cm.output)\n        )\n\n    def test_010_smtp_username_no_password(self):\n        \"\"\"Logs error when username is set but password is missing\"\"\"\n        self.hooks.config_dic[\"Hooks\"][\"smtp_username\"] = \"user\"\n        self.hooks.config_dic[\"Hooks\"].pop(\"smtp_password\", None)\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(\n            any(\n                \"SMTP username provided but password is missing\" in msg\n                for msg in cm.output\n            )\n        )\n\n    def test_011_smtp_both_tls_and_starttls(self):\n        \"\"\"Logs warning if both smtp_use_tls and smtp_use_starttls are True\"\"\"\n        self.hooks.config_dic[\"Hooks\"][\"smtp_use_tls\"] = True\n        self.hooks.config_dic[\"Hooks\"][\"smtp_use_starttls\"] = True\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(\n            any(\n                \"Both smtp_use_tls and smtp_use_starttls are enabled\" in msg\n                for msg in cm.output\n            )\n        )\n\n    def test_012_smtp_port_465_without_tls(self):\n        \"\"\"Logs info if port 465 is used without TLS\"\"\"\n        self.hooks.config_dic[\"Hooks\"][\"smtp_port\"] = 465\n        self.hooks.config_dic[\"Hooks\"][\"smtp_use_tls\"] = False\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(\n            any(\"Port 465 typically requires TLS\" in msg for msg in cm.output)\n        )\n\n    def test_013_smtp_port_587_without_tls_or_starttls(self):\n        \"\"\"Logs info if port 587 is used without TLS or STARTTLS\"\"\"\n        self.hooks.config_dic[\"Hooks\"][\"smtp_port\"] = 587\n        self.hooks.config_dic[\"Hooks\"][\"smtp_use_tls\"] = False\n        self.hooks.config_dic[\"Hooks\"][\"smtp_use_starttls\"] = False\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._validate_smtp_configuration()\n        self.assertTrue(\n            any(\"Port 587 typically requires STARTTLS\" in msg for msg in cm.output)\n        )\n\n    def test_014_load_configuration_assigns_required_fields(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n            }\n        )\n        self.hooks._load_configuration()\n        self.assertEqual(self.hooks.appname, \"TestApp\")\n        self.assertEqual(self.hooks.sender, \"test@example.com\")\n        self.assertEqual(self.hooks.rcpt, \"rcpt@example.com\")\n\n    def test_015_load_configuration_optional_booleans(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n                \"report_failures\": \"False\",\n                \"report_successes\": \"True\",\n            }\n        )\n        self.hooks._load_configuration()\n        self.assertFalse(self.hooks.report_failures)\n        self.assertTrue(self.hooks.report_successes)\n\n    def test_016_load_configuration_smtp_defaults(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n            }\n        )\n        self.hooks._load_configuration()\n        self.assertEqual(self.hooks.smtp_server, \"localhost\")\n        self.assertEqual(self.hooks.smtp_port, 25)\n        self.assertEqual(self.hooks.smtp_timeout, 30)\n        self.assertIsNone(self.hooks.smtp_username)\n        self.assertIsNone(self.hooks.smtp_password)\n        self.assertTrue(self.hooks.smtp_use_tls)\n        self.assertFalse(self.hooks.smtp_use_starttls)\n\n    def test_017_load_configuration_assigns_all_fields(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n                \"smtp_server\": \"smtp.example.com\",\n                \"smtp_port\": \"2525\",\n                \"subject_prefix\": \"[PREFIX]\",\n                \"smtp_timeout\": \"42\",\n                \"smtp_username\": \"user\",\n                \"smtp_password\": \"pass\",\n                \"smtp_use_tls\": \"False\",\n                \"smtp_use_starttls\": \"True\",\n            }\n        )\n        self.hooks._load_configuration()\n        self.assertEqual(self.hooks.smtp_server, \"smtp.example.com\")\n        self.assertEqual(self.hooks.smtp_port, 2525)\n        self.assertEqual(self.hooks.email_subject_prefix, \"[PREFIX]\")\n        self.assertEqual(self.hooks.smtp_timeout, 42)\n        self.assertEqual(self.hooks.smtp_username, \"user\")\n        self.assertEqual(self.hooks.smtp_password, \"pass\")\n        self.assertFalse(self.hooks.smtp_use_tls)\n        self.assertTrue(self.hooks.smtp_use_starttls)\n\n    def test_018_load_configuration_uses_sender_as_username_if_password_only(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n                \"smtp_password\": \"pass\",\n            }\n        )\n        with self.assertLogs(self.logger, \"DEBUG\") as cm:\n            self.hooks._load_configuration()\n        self.assertEqual(self.hooks.smtp_username, \"test@example.com\")\n        self.assertIn(\"Using sender email as SMTP username\", \"\\n\".join(cm.output))\n\n    def test_019_load_configuration_sets_envelope_fields(self):\n        self.hooks.config_dic[\"Hooks\"].update(\n            {\n                \"appname\": \"TestApp\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"rcpt@example.com\",\n            }\n        )\n        self.hooks._load_configuration()\n        self.assertIn(\"From\", self.hooks.envelope)\n        self.assertIn(\"To\", self.hooks.envelope)\n        self.assertIn(\"Date\", self.hooks.envelope)\n        self.assertEqual(self.hooks.envelope[\"From\"], \"TestApp <test@example.com>\")\n        self.assertEqual(self.hooks.envelope[\"To\"], \"rcpt@example.com\")\n        self.assertFalse(self.hooks.done)\n\n    def test_059_done_already_sent_warning(self):\n        \"\"\"_done warns when called multiple times\"\"\"\n        self.hooks.done = True\n\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._done()\n\n        self.assertIn(\"email already sent\", \"\\n\".join(cm.output))\n\n    def test_060_config_from_default_section(self):\n        \"\"\"Configuration loads from DEFAULT section when Hooks section is missing values\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\n                \"appname\": \"default-app\",\n                \"sender\": \"default@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"smtp_server\": \"default.smtp.com\",\n                \"smtp_port\": \"465\",\n            },\n            \"Hooks\": {},\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            self.assertEqual(h.appname, \"default-app\")\n            self.assertEqual(h.sender, \"default@example.com\")\n            self.assertEqual(h.smtp_server, \"default.smtp.com\")\n            self.assertEqual(h.smtp_port, 465)\n\n    def test_061_config_hooks_precedence_over_default(self):\n        \"\"\"Configuration in Hooks section takes precedence over DEFAULT section\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\n                \"appname\": \"default-app\",\n                \"sender\": \"default@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"smtp_server\": \"default.smtp.com\",\n                \"smtp_port\": \"25\",\n            },\n            \"Hooks\": {\n                \"appname\": \"hooks-app\",  # Should override DEFAULT\n                \"sender\": \"hooks@example.com\",  # Should override DEFAULT\n                \"rcpt\": \"admin@example.com\"\n                # smtp_server and smtp_port should come from DEFAULT\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            self.assertEqual(h.appname, \"hooks-app\")  # From Hooks section\n            self.assertEqual(h.sender, \"hooks@example.com\")  # From Hooks section\n            self.assertEqual(h.smtp_server, \"default.smtp.com\")  # From DEFAULT section\n            self.assertEqual(h.smtp_port, 25)  # From DEFAULT section\n\n    def test_062_config_missing_both_sections_fails(self):\n        \"\"\"Configuration validation fails when neither Hooks nor DEFAULT section exists\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\"SomeOther\": {\"key\": \"value\"}}\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            with self.assertRaises(ValueError) as ctx:\n                Hooks(self.logger)\n            self.assertIn(\"Missing 'Hooks' or 'DEFAULT' section\", str(ctx.exception))\n\n    def test_063_config_required_keys_from_mixed_sections(self):\n        \"\"\"Required keys can be satisfied from a mix of Hooks and DEFAULT sections\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\n                \"sender\": \"default@example.com\",\n                \"smtp_server\": \"default.smtp.com\",\n            },\n            \"Hooks\": {\n                \"appname\": \"hooks-app\",\n                \"rcpt\": \"admin@example.com\"\n                # sender comes from DEFAULT\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            self.assertEqual(h.appname, \"hooks-app\")  # From Hooks\n            self.assertEqual(h.sender, \"default@example.com\")  # From DEFAULT\n            self.assertEqual(h.rcpt, \"admin@example.com\")  # From Hooks\n            self.assertEqual(h.smtp_server, \"default.smtp.com\")  # From DEFAULT\n\n    def test_064_get_config_int_from_hooks_section(self):\n        \"\"\"_get_config_int retrieves integer value from Hooks section\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\"smtp_port\": \"25\", \"timeout\": \"60\"},\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"smtp_port\": \"465\",  # Should override DEFAULT\n                \"connection_timeout\": \"30\",\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Test values from Hooks section\n            self.assertEqual(h._get_config_int(\"smtp_port\"), 465)\n            self.assertEqual(h._get_config_int(\"connection_timeout\"), 30)\n            # Test value from DEFAULT section when not in Hooks\n            self.assertEqual(h._get_config_int(\"timeout\"), 60)\n\n    def test_065_get_config_int_fallback_to_default_section(self):\n        \"\"\"_get_config_int falls back to DEFAULT section when key not in Hooks\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\"smtp_port\": \"587\", \"smtp_timeout\": \"45\"},\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\"\n                # No smtp_port or smtp_timeout in Hooks\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Values should come from DEFAULT section\n            self.assertEqual(h._get_config_int(\"smtp_port\"), 587)\n            self.assertEqual(h._get_config_int(\"smtp_timeout\"), 45)\n\n    def test_066_get_config_int_with_fallback_value(self):\n        \"\"\"_get_config_int returns fallback when key not found in either section\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Should return fallback value\n            self.assertEqual(h._get_config_int(\"missing_key\", 999), 999)\n            self.assertIsNone(h._get_config_int(\"missing_key\"))\n\n    def test_067_get_config_int_invalid_conversion(self):\n        \"\"\"_get_config_int returns fallback when value cannot be converted to int\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"invalid_port\": \"not_a_number\",\n                \"float_value\": \"25.5\",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Should return fallback for invalid values\n            self.assertEqual(h._get_config_int(\"invalid_port\", 25), 25)\n            self.assertEqual(h._get_config_int(\"float_value\", 80), 80)\n            self.assertIsNone(h._get_config_int(\"invalid_port\"))\n\n    def test_068_get_config_int_edge_cases(self):\n        \"\"\"_get_config_int handles edge cases like empty strings and zero\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\"zero_value\": \"0\", \"negative_value\": \"-1\"},\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"empty_value\": \"\",\n                \"whitespace_value\": \"  123  \",\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Valid conversions\n            self.assertEqual(h._get_config_int(\"zero_value\"), 0)\n            self.assertEqual(h._get_config_int(\"negative_value\"), -1)\n            self.assertEqual(h._get_config_int(\"whitespace_value\"), 123)\n            # Empty string should return fallback\n            self.assertEqual(h._get_config_int(\"empty_value\", 42), 42)\n\n    def test_069_get_config_boolean_from_hooks_section(self):\n        \"\"\"_get_config_boolean retrieves boolean value from Hooks section\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\"ssl_use\": \"false\", \"debug_mode\": \"0\"},\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"ssl_use\": \"true\",  # Should override DEFAULT\n                \"smtp_use_starttls\": \"yes\",\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Test values from Hooks section\n            self.assertTrue(h._get_config_boolean(\"ssl_use\"))\n            self.assertTrue(h._get_config_boolean(\"smtp_use_starttls\"))\n            # Test value from DEFAULT section when not in Hooks\n            self.assertFalse(h._get_config_boolean(\"debug_mode\"))\n\n    def test_070_get_config_boolean_fallback_to_default_section(self):\n        \"\"\"_get_config_boolean falls back to DEFAULT section when key not in Hooks\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"DEFAULT\": {\"smtp_use_tls\": \"true\", \"ssl_noverify\": \"1\"},\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\"\n                # No boolean values in Hooks\n            },\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Values should come from DEFAULT section\n            self.assertTrue(h._get_config_boolean(\"smtp_use_tls\"))\n            self.assertTrue(h._get_config_boolean(\"ssl_noverify\"))\n\n    def test_071_get_config_boolean_various_true_values(self):\n        \"\"\"_get_config_boolean recognizes various true value formats\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"bool_true\": \"true\",\n                \"bool_True\": \"True\",\n                \"bool_TRUE\": \"TRUE\",\n                \"bool_1\": \"1\",\n                \"bool_yes\": \"yes\",\n                \"bool_YES\": \"YES\",\n                \"bool_on\": \"on\",\n                \"bool_ON\": \"ON\",\n                \"bool_with_spaces\": \"  true  \",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # All should evaluate to True\n            self.assertTrue(h._get_config_boolean(\"bool_true\"))\n            self.assertTrue(h._get_config_boolean(\"bool_True\"))\n            self.assertTrue(h._get_config_boolean(\"bool_TRUE\"))\n            self.assertTrue(h._get_config_boolean(\"bool_1\"))\n            self.assertTrue(h._get_config_boolean(\"bool_yes\"))\n            self.assertTrue(h._get_config_boolean(\"bool_YES\"))\n            self.assertTrue(h._get_config_boolean(\"bool_on\"))\n            self.assertTrue(h._get_config_boolean(\"bool_ON\"))\n            self.assertTrue(h._get_config_boolean(\"bool_with_spaces\"))\n\n    def test_072_get_config_boolean_various_false_values(self):\n        \"\"\"_get_config_boolean recognizes various false value formats\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"bool_false\": \"false\",\n                \"bool_False\": \"False\",\n                \"bool_FALSE\": \"FALSE\",\n                \"bool_0\": \"0\",\n                \"bool_no\": \"no\",\n                \"bool_NO\": \"NO\",\n                \"bool_off\": \"off\",\n                \"bool_OFF\": \"OFF\",\n                \"bool_empty\": \"\",\n                \"bool_random\": \"random_text\",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # All should evaluate to False\n            self.assertFalse(h._get_config_boolean(\"bool_false\"))\n            self.assertFalse(h._get_config_boolean(\"bool_False\"))\n            self.assertFalse(h._get_config_boolean(\"bool_FALSE\"))\n            self.assertFalse(h._get_config_boolean(\"bool_0\"))\n            self.assertFalse(h._get_config_boolean(\"bool_no\"))\n            self.assertFalse(h._get_config_boolean(\"bool_NO\"))\n            self.assertFalse(h._get_config_boolean(\"bool_off\"))\n            self.assertFalse(h._get_config_boolean(\"bool_OFF\"))\n            self.assertFalse(h._get_config_boolean(\"bool_empty\"))\n            self.assertFalse(h._get_config_boolean(\"bool_random\"))\n\n    def test_073_get_config_boolean_with_fallback_value(self):\n        \"\"\"_get_config_boolean returns fallback when key not found in either section\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n            }\n        }\n        with patch(\n            \"examples.hooks.email_hooks.load_config\", return_value=DummyConfig(cfg)\n        ):\n            h = Hooks(self.logger)\n            # Should return fallback value\n            self.assertTrue(h._get_config_boolean(\"missing_key\", True))\n            self.assertFalse(h._get_config_boolean(\"missing_key\", False))\n            self.assertIsNone(h._get_config_boolean(\"missing_key\"))\n\n    def test_074_get_config_boolean_already_boolean_type(self):\n        \"\"\"_get_config_boolean handles values that are already boolean type\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        cfg = {\n            \"Hooks\": {\n                \"appname\": \"test-app\",\n                \"sender\": \"test@example.com\",\n                \"rcpt\": \"admin@example.com\",\n                \"bool_true\": True,  # Actual boolean, not string\n                \"bool_false\": False,  # Actual boolean, not string\n            }\n        }\n\n        # Extend DummyConfig to handle boolean types\n        config = DummyConfig(cfg)\n\n        with patch(\"examples.hooks.email_hooks.load_config\", return_value=config):\n            h = Hooks(self.logger)\n            # Should handle actual boolean values correctly\n            self.assertTrue(h._get_config_boolean(\"bool_true\"))\n            self.assertFalse(h._get_config_boolean(\"bool_false\"))\n\n    def test_021_done_handles_exception_and_logs_error(self):\n        self.hooks.smtp_use_tls = True\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 465\n        self.hooks.smtp_timeout = 10\n        self.hooks.smtp_username = \"user\"\n        self.hooks.smtp_password = \"pass\"\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        with patch(\"smtplib.SMTP_SSL\", side_effect=Exception(\"SMTP error\")):\n            with self.assertLogs(self.logger, \"ERROR\") as cm:\n                self.hooks._done()\n            self.assertIn(\"Email sending failed\", \"\\n\".join(cm.output))\n            self.assertTrue(self.hooks.done)\n\n    def test_022_clean_san_valid_list(self):\n        \"\"\"_clean_san returns correct value for valid SAN list\"\"\"\n        result = self.hooks._clean_san([\"DNS:example.com\"])\n        self.assertEqual(result, \"example.com\")\n\n    def test_023_clean_san_none(self):\n        \"\"\"_clean_san returns 'unknown' and logs warning for None input\"\"\"\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            result = self.hooks._clean_san(None)\n        self.assertEqual(result, \"unknown\")\n        self.assertTrue(any(\"Empty SAN list provided\" in msg for msg in cm.output))\n\n    def test_024_clean_san_not_a_list(self):\n        \"\"\"_clean_san returns 'unknown' and logs warning for non-list input\"\"\"\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            result = self.hooks._clean_san(\"DNS:example.com\")\n        self.assertEqual(result, \"unknown\")\n        self.assertTrue(any(\"SAN is not a list\" in msg for msg in cm.output))\n\n    def test_025_clean_san_invalid_format(self):\n        \"\"\"_clean_san returns 'unknown' and logs warning for invalid format\"\"\"\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            result = self.hooks._clean_san([\"example.com\"])\n        self.assertEqual(result, \"unknown\")\n        self.assertTrue(any(\"Invalid SAN format\" in msg for msg in cm.output))\n\n    @patch(\"examples.hooks.email_hooks.build_pem_file\", return_value=\"PEM DATA\")\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_026_attach_csr_success(self, mock_mimeapp, mock_build_pem):\n        \"\"\"_attach_csr attaches CSR as expected when PEM is built\"\"\"\n        request_key = \"reqkey\"\n        csr = \"csrdata\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        part_mock = MagicMock()\n        mock_mimeapp.return_value = part_mock\n        self.hooks._attach_csr(request_key, csr)\n        mock_build_pem.assert_called()\n        mock_mimeapp.assert_called_with(\"PEM DATA\", Name=\"example.com_reqkey.csr\")\n        self.hooks.envelope.attach.assert_called_with(part_mock)\n        self.assertIn(\n            \"To read example.com_reqkey.csr using CMD on Windows\",\n            \"\\n\".join(self.hooks.msg),\n        )\n\n    @patch(\"examples.hooks.email_hooks.build_pem_file\", return_value=None)\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_027_attach_csr_pem_build_fails(self, mock_mimeapp, mock_build_pem):\n        \"\"\"_attach_csr logs error and does not attach if PEM build fails\"\"\"\n        request_key = \"reqkey\"\n        csr = \"csrdata\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks._attach_csr(request_key, csr)\n        mock_build_pem.assert_called()\n        mock_mimeapp.assert_not_called()\n        self.assertTrue(\n            any(\"Failed to build PEM file from CSR\" in msg for msg in cm.output)\n        )\n\n    @patch(\"examples.hooks.email_hooks.build_pem_file\", side_effect=Exception(\"fail\"))\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_028_attach_csr_exception(self, mock_mimeapp, mock_build_pem):\n        \"\"\"_attach_csr logs warning and appends message if exception occurs\"\"\"\n        request_key = \"reqkey\"\n        csr = \"csrdata\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._attach_csr(request_key, csr)\n        self.assertTrue(any(\"Failed to attach CSR\" in msg for msg in cm.output))\n        self.assertIn(\"CSR attachment failed: Exception\", self.hooks.msg[-1])\n\n    @patch(\n        \"examples.hooks.email_hooks.x509.load_pem_x509_certificates\",\n        return_value=[MagicMock(), MagicMock()],\n    )\n    @patch(\n        \"examples.hooks.email_hooks.pkcs12.serialize_key_and_certificates\",\n        return_value=b\"PFXDATA\",\n    )\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_029_attach_cert_success(\n        self, mock_mimeapp, mock_serialize, mock_load_x509\n    ):\n        \"\"\"_attach_cert attaches certificate as expected when parsing and serialization succeed\"\"\"\n        request_key = \"reqkey\"\n        certificate = \"CERTDATA\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        part_mock = MagicMock()\n        mock_mimeapp.return_value = part_mock\n        self.hooks._attach_cert(request_key, certificate)\n        mock_load_x509.assert_called_with(certificate.encode(\"utf-8\"))\n        mock_serialize.assert_called()\n        mock_mimeapp.assert_called_with(b\"PFXDATA\", Name=\"example.com_reqkey.pfx\")\n        self.hooks.envelope.attach.assert_called_with(part_mock)\n        self.assertIn(\n            \"To read example.com_reqkey.pfx using CMD on Windows\",\n            \"\\n\".join(self.hooks.msg),\n        )\n\n    @patch(\n        \"examples.hooks.email_hooks.x509.load_pem_x509_certificates\",\n        side_effect=Exception(\"parsefail\"),\n    )\n    @patch(\"examples.hooks.email_hooks.pkcs12.serialize_key_and_certificates\")\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_030_attach_cert_parse_error(\n        self, mock_mimeapp, mock_serialize, mock_load_x509\n    ):\n        \"\"\"_attach_cert logs warning and appends message if certificate parsing fails\"\"\"\n        request_key = \"reqkey\"\n        certificate = \"CERTDATA\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._attach_cert(request_key, certificate)\n        self.assertTrue(\n            any(\"Certificate attachment failed\" in msg for msg in cm.output)\n        )\n        self.assertIn(\"Certificate attachment failed: Exception\", self.hooks.msg[-1])\n        mock_serialize.assert_not_called()\n        mock_mimeapp.assert_not_called()\n\n    @patch(\n        \"examples.hooks.email_hooks.x509.load_pem_x509_certificates\",\n        return_value=[MagicMock(), MagicMock()],\n    )\n    @patch(\n        \"examples.hooks.email_hooks.pkcs12.serialize_key_and_certificates\",\n        side_effect=Exception(\"serializefail\"),\n    )\n    @patch(\"examples.hooks.email_hooks.MIMEApplication\")\n    def test_031_attach_cert_serialize_error(\n        self, mock_mimeapp, mock_serialize, mock_load_x509\n    ):\n        \"\"\"_attach_cert logs warning and appends message if serialization fails\"\"\"\n        request_key = \"reqkey\"\n        certificate = \"CERTDATA\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = MagicMock()\n        self.hooks.msg = []\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._attach_cert(request_key, certificate)\n        self.assertTrue(\n            any(\"Certificate attachment failed\" in msg for msg in cm.output)\n        )\n        self.assertIn(\"Certificate attachment failed: Exception\", self.hooks.msg[-1])\n        mock_mimeapp.assert_not_called()\n\n    def test_032_format_subject_with_prefix(self):\n        \"\"\"_format_subject includes prefix if set\"\"\"\n        self.hooks.appname = \"TestApp\"\n        self.hooks.email_subject_prefix = \"[PREFIX]\"\n        subject = self.hooks._format_subject(\"success\", \"example.com\")\n        self.assertTrue(subject.startswith(\"[PREFIX] \"))\n        self.assertIn(\"TestApp success: example.com\", subject)\n\n    def test_033_format_subject_without_prefix(self):\n        \"\"\"_format_subject omits prefix if not set\"\"\"\n        self.hooks.appname = \"TestApp\"\n        self.hooks.email_subject_prefix = \"\"\n        subject = self.hooks._format_subject(\"failure\", \"example.com\")\n        self.assertEqual(subject, \"TestApp failure: example.com\")\n\n    def test_034_format_message_header_success(self):\n        \"\"\"_format_message_header returns expected header for success\"\"\"\n        self.hooks.appname = \"TestApp\"\n        header = self.hooks._format_message_header(\"success\", \"example.com\")\n        self.assertIn(\"ACME Certificate Success Notification\", header)\n        self.assertIn(\"Application: TestApp\", header)\n        self.assertIn(\"Subject Alternative Name: example.com\", header)\n        self.assertIn(\"Timestamp:\", header)\n        self.assertIn(\"-\" * 50, header)\n\n    def test_035_format_message_header_failure(self):\n        \"\"\"_format_message_header returns expected header for failure\"\"\"\n        self.hooks.appname = \"TestApp\"\n        header = self.hooks._format_message_header(\"failure\", \"test-san\")\n        self.assertIn(\"ACME Certificate Failure Notification\", header)\n        self.assertIn(\"Application: TestApp\", header)\n        self.assertIn(\"Subject Alternative Name: test-san\", header)\n        self.assertIn(\"Timestamp:\", header)\n        self.assertIn(\"-\" * 50, header)\n\n    @patch(\"examples.hooks.email_hooks.csr_san_get\", return_value=[\"DNS:example.com\"])\n    def test_036_post_hook_success(self, mock_csr_san_get):\n        \"\"\"post_hook sends failure email with correct subject and message\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        # Setup a real Hooks instance with mocks for envelope and _done\n        hooks = Hooks(self.logger)\n        hooks.san = \"example.com\"\n        hooks.envelope = {\"Subject\": None}\n        hooks._format_subject = lambda status, san: f\"subject-{status}-{san}\"\n        hooks._format_message_header = lambda status, san: f\"header-{status}-{san}\"\n        hooks._attach_csr = MagicMock()\n        hooks._done = MagicMock()\n        hooks.msg = []\n        hooks.report_failures = True\n        hooks.post_hook(\"reqkey\", \"order\", \"csr\", \"error-details\")\n        self.assertEqual(hooks.envelope[\"Subject\"], \"subject-failure-example.com\")\n        self.assertIn(\"header-failure-example.com\", hooks.msg[0])\n        self.assertIn(\"Error Details\", hooks.msg[1])\n        hooks._attach_csr.assert_called_with(\"reqkey\", \"csr\")\n        hooks._done.assert_called()\n\n    def test_037_post_hook_report_failures_false(self):\n        \"\"\"post_hook does nothing if report_failures is False\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        hooks = Hooks(self.logger)\n        hooks.report_failures = False\n        hooks._done = MagicMock()\n        hooks._attach_csr = MagicMock()\n        hooks.envelope = {\"Subject\": None}\n        hooks.msg = []\n        hooks.post_hook(\"reqkey\", \"order\", \"csr\", \"error-details\")\n        hooks._done.assert_not_called()\n        hooks._attach_csr.assert_not_called()\n        self.assertEqual(hooks.msg, [])\n\n    @patch(\"examples.hooks.email_hooks.csr_san_get\", side_effect=Exception(\"fail\"))\n    def test_038_post_hook_exception(self, mock_csr_san_get):\n        \"\"\"post_hook logs error if exception occurs\"\"\"\n        from examples.hooks.email_hooks import Hooks\n\n        hooks = Hooks(self.logger)\n        hooks.report_failures = True\n        hooks._done = MagicMock()\n        hooks._attach_csr = MagicMock()\n        hooks.envelope = {\"Subject\": None}\n        hooks.msg = []\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            hooks.post_hook(\"reqkey\", \"order\", \"csr\", \"error-details\")\n        self.assertTrue(any(\"Error in post_hook\" in msg for msg in cm.output))\n\n    @patch(\"examples.hooks.email_hooks.cert_san_get\", return_value=[\"DNS:example.com\"])\n    def test_039_success_hook_normal(self, mock_cert_san_get):\n        \"\"\"success_hook sends success email with correct subject and message\"\"\"\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = {\"Subject\": None}\n        self.hooks._format_subject = lambda status, san: f\"subject-{status}-{san}\"\n        self.hooks._format_message_header = lambda status, san: f\"header-{status}-{san}\"\n        self.hooks._attach_csr = MagicMock()\n        self.hooks._attach_cert = MagicMock()\n        self.hooks._done = MagicMock()\n        self.hooks.msg = []\n        self.hooks.report_successes = True\n        self.hooks.success_hook(\"reqkey\", \"order\", \"csr\", \"cert\", \"cert_raw\", \"pollid\")\n        self.assertEqual(self.hooks.envelope[\"Subject\"], \"subject-success-example.com\")\n        self.assertIn(\"header-success-example.com\", self.hooks.msg[0])\n        self.assertIn(\"Certificate issued successfully!\", self.hooks.msg[1])\n        self.hooks._attach_csr.assert_called_with(\"reqkey\", \"csr\")\n        self.hooks._attach_cert.assert_called_with(\"reqkey\", \"cert\")\n        self.hooks._done.assert_called()\n\n    def test_040_success_hook_report_successes_false(self):\n        \"\"\"success_hook does nothing if report_successes is False\"\"\"\n        self.hooks.report_successes = False\n        self.hooks._done = MagicMock()\n        self.hooks._attach_csr = MagicMock()\n        self.hooks._attach_cert = MagicMock()\n        self.hooks.envelope = {\"Subject\": None}\n        self.hooks.msg = []\n        self.hooks.success_hook(\"reqkey\", \"order\", \"csr\", \"cert\", \"cert_raw\", \"pollid\")\n        self.hooks._done.assert_not_called()\n        self.hooks._attach_csr.assert_not_called()\n        self.hooks._attach_cert.assert_not_called()\n        self.assertEqual(self.hooks.msg, [])\n\n    @patch(\"examples.hooks.email_hooks.cert_san_get\", side_effect=Exception(\"fail\"))\n    def test_041_success_hook_exception(self, mock_cert_san_get):\n        \"\"\"success_hook logs error if exception occurs\"\"\"\n        self.hooks.report_successes = True\n        self.hooks._done = MagicMock()\n        self.hooks._attach_csr = MagicMock()\n        self.hooks._attach_cert = MagicMock()\n        self.hooks.envelope = {\"Subject\": None}\n        self.hooks.msg = []\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks.success_hook(\n                \"reqkey\", \"order\", \"csr\", \"cert\", \"cert_raw\", \"pollid\"\n            )\n        self.assertTrue(any(\"Error in success_hook\" in msg for msg in cm.output))\n\n    @patch(\"examples.hooks.email_hooks.cert_san_get\", return_value=[\"DNS:example.com\"])\n    def test_042_success_hook_normal(self, mock_cert_san_get):\n        \"\"\"success_hook sends success email with correct subject and message\"\"\"\n\n        certificate = (\n            \"-----BEGIN CERTIFICATE-----\\nMIIECjCCAnKgAwIBAgIRALGHaaUUFeRgIrUBibd8K3owDQYJKoZ\"\n            \"IhvcNAQELBQAw\\nVTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRUwEwYDVQQLDAxyb290\"\n            \"\\nQGJhc3Rpb24xHDAaBgNVBAMME21rY2VydCByb290QGJhc3Rpb24wHhcNMjUxMDAx\\nMTQyMjMxWhcN\"\n            \"MjgwMTAxMTQyMjMxWjBAMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxv\\ncG1lbnQgY2VydGlmaWNhdGUxFT\"\n            \"ATBgNVBAsMDHJvb3RAYmFzdGlvbjCCASIwDQYJ\\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVJJSx0\"\n            \"7B5xVBF7iT1jwvP9Q7sQHBAa\\nOSctCmm8FMgQAn0B1i/M5RORrxmsxe9TGYQN23mgZPrkhfFREbK3jF\"\n            \"1qDyi5aqyv\\nRUCY8c6V8gVNHqeFY/Fbo7eVpUmL6cEWCQa4/IyC8HZgWZPvK8DiNEKTS6fa++Wg\\ng7\"\n            \"hEl0Du9IENEdnJZ8S63UGUklNaUmn/lsD2SMgtDq0OJUYmU5Zn1Uryh8I4MJCu\\nHY/+i4CV+6tirKYN\"\n            \"eQYvX2lxY8AcYnRsg8x18IVO5fu7DoH18uK0YtlTMEYac+AX\\nOI/6B0C6NqXse71cQs53UF/O7ew+OC\"\n            \"kZ67CoYobAqeuiOVEEA+qTSUsCAwEAAaNq\\nMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG\"\n            \"AQUFBwMBMB8GA1UdIwQY\\nMBaAFEW2GtPZX80jY6cvOq8rMMAfW1hsMCAGA1UdEQQZMBeCFXNvbWV0aG\"\n            \"luZy5l\\neGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAkDCKBHuqVxcXgx7vhftzDE3M\\nj8x7WC\"\n            \"4di+rkIrxyJ3ulGHc7Pl2gyvMoKJxRCqcK4WgLH7AqDkRsQSF+/yvv+c0H\\nbUYjauPfDo1yUlLIQpo3\"\n            \"7uwJjsfQt4j/AFLpYHw2myqAsqMw1jwbXRuLyyiHWSay\\nljyHhWVnbZcLZNvBwL6bV0RCuRlWCFfjlA\"\n            \"6buXW3a23krjs8k5I4UhKaeX7d0Pvk\\nx/3JxjlGlOA8tYBT8+6Aq1xOIC1MuD8h/32Cxa7vDI9VyspY\"\n            \"bsbCBl5m2XD566/P\\nRE5rn62kBBHEXiIpFrE0R1d8MFTx9PEC00jVFDWnec3Ayl2TiTpptCF/Cb5S9K\"\n            \"6g\\nEdUFUkQj9dTxX8owUbm/tYGIYrwibWzTtscb75KjSzExnZApMfNgngke8r1f6P4Y\\nHRQQU7/0Bc\"\n            \"Di2GPzCy83rN3d2DFn6U66TZG0EEEdV1e1A0gsqfgx/b6YAZsZv26H\\nZ5IkqXdj3IZRDcwdYgaTrlsl\"\n            \"kPsantdPl+x/kxP5\\n-----END CERTIFICATE-----\\n\\n-----BEGIN CERTIFICATE-----\\nMIIE\"\n            \"ejCCAuKgAwIBAgIRAI5dQJ4OEZYF5sy28Iw+/zkwDQYJKoZIhvcNAQELBQAw\\nVTEeMBwGA1UEChMVbW\"\n            \"tjZXJ0IGRldmVsb3BtZW50IENBMRUwEwYDVQQLDAxyb290\\nQGJhc3Rpb24xHDAaBgNVBAMME21rY2Vy\"\n            \"dCByb290QGJhc3Rpb24wHhcNMjUxMDAx\\nMTQyMjMwWhcNMzUxMDAxMTQyMjMwWjBVMR4wHAYDVQQKEx\"\n            \"Vta2NlcnQgZGV2ZWxv\\ncG1lbnQgQ0ExFTATBgNVBAsMDHJvb3RAYmFzdGlvbjEcMBoGA1UEAwwTbWtj\"\n            \"ZXJ0\\nIHJvb3RAYmFzdGlvbjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKo5\\nMM8/lupi\"\n            \"8cOQqh5igXfGFrunERIiShzhV3EHVpQN+h3SU0BQF50DZHDTL1rHQqAn\\nhPK4fgZ37s9HjssysejgYK\"\n            \"61w9YgvoOd6dlsCTSYjpF19T9Dz5SY8yZz3lNLHcbg\\nN111PZP4hyN3BtNw4ttENGuKAqHgvFO/xmzM\"\n            \"gJtT62G4qq8VwHa8ktFa3b9Lh14/\\njEOjUIkgAgHE869/deebb2ENox7nL+W0VB9o0XCqMDYF0ZF6pw\"\n            \"4gVP2FgNbwjSgM\\nci/NCW99biGHOKA5LVG4d6nNxFgOg7GdEFExzzHjjyIYQBC/ZB7ulDyQQ6KcQRn5\"\n            \"\\nbvn83SuUZ1cGRSWSndosR3LhEJaxDLbr68X7byL7PNkBM4ILAGpd+oZLCM4Z9cpF\\njGW4GxilijEg\"\n            \"Smo7gLZk++oEh3O31Wt5dyGs2BHeUDf0rHG7z+agpzK0H6Ar9Rj5\\nurfDJvswioyU7jUxrpOg+4Wk/J\"\n            \"aJWncbU49fZRtAiwYZVVHyvKf5bn+bRJK+bQID\\nAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0T\"\n            \"AQH/BAgwBgEB/wIBADAdBgNV\\nHQ4EFgQURbYa09lfzSNjpy86ryswwB9bWGwwDQYJKoZIhvcNAQELBQ\"\n            \"ADggGBAF+y\\nWudDZVtWEbNpsSz5YvZ3W0BuNwaFo5TFYhzhh4ougs/SUhvPW5dAsVBJBjTgJ4fy\\nXm\"\n            \"miptcVzrvZiaB2+muL1PT/vUhFomuyqw46smzBIrUyHHmjqdoVIhmJ4XJq/eLS\\n7wMLDpTeH3kQaQWt\"\n            \"cK1EqlPOIMn5m/st663280lB2ICyv1zSQgWIkv4YpmzAuJcm\\nwYw899emEsSdf3q1lQoLR0NkBdRPSN\"\n            \"Zcnb9+wR98Iw5Rjca/7P0A1RbbEmbayXzf\\n4adhIZaaCBDhADcU6SBC5v8HsIj0tolyf7nTKarKJoKy\"\n            \"eY1i1sXrK28vZyWykLLD\\nQ7FHcRDfoAtJ2QUvxbpBXpDg/F79PDjrdjc6n8nn4RG+JIwO8j7t3GMB5c\"\n            \"MWOnKC\\nruQ4NuKcsWkcIaQIcxJTx+tOYyGqyAMzxA+VFTQ+HNjcFBnue/XJOya4dpOo1BEG\\nAacSqy\"\n            \"ipP2lMM8Xbje7snzwmutRdATxiyGKDzacEJWUMHzlkrX8WsFIUnVNMUA==\\n-----END CERTIFICATE\"\n            \"-----\"\n        )\n        self.hooks.san = \"example.com\"\n        self.hooks.envelope = {\"Subject\": None}\n        self.hooks._format_subject = lambda status, san: f\"subject-{status}-{san}\"\n        self.hooks._format_message_header = lambda status, san: f\"header-{status}-{san}\"\n        self.hooks._attach_csr = MagicMock()\n        self.hooks._attach_cert = MagicMock()\n        self.hooks._done = MagicMock()\n        self.hooks.msg = []\n        self.hooks.report_successes = True\n        with self.assertLogs(self.logger, level=\"DEBUG\") as cm:\n            self.hooks.success_hook(\n                \"reqkey\", \"order\", \"csr\", certificate, \"cert_raw\", \"pollid\"\n            )\n        self.assertTrue(\n            any(\"Parsing certificate details for email\" in msg for msg in cm.output)\n        )\n        self.assertEqual(self.hooks.envelope[\"Subject\"], \"subject-success-example.com\")\n        self.assertIn(\"header-success-example.com\", self.hooks.msg[0])\n        self.assertIn(\"Certificate issued successfully!\", self.hooks.msg[1])\n        self.hooks._attach_csr.assert_called_with(\"reqkey\", \"csr\")\n        self.hooks._attach_cert.assert_called_with(\"reqkey\", certificate)\n        self.hooks._done.assert_called()\n\n    @patch(\"examples.hooks.email_hooks.cert_san_get\", return_value=[\"DNS:example.com\"])\n    @patch(\"examples.hooks.email_hooks.x509.load_pem_x509_certificates\")\n    def test_043_success_hook_cert_not_valid_before_utc_exception(\n        self, mock_load_x509, mock_cert_san_get\n    ):\n        \"\"\"success_hook handles exception in cert.not_valid_before_utc and logs error\"\"\"\n        # Create a mock cert with not_valid_before_utc raising\n        cert_mock = MagicMock()\n        type(cert_mock).serial_number = PropertyMock(return_value=123)\n        type(cert_mock).not_valid_before_utc = PropertyMock(\n            side_effect=Exception(\"fail not_valid_before_utc\")\n        )\n        type(cert_mock).not_valid_after_utc = PropertyMock(return_value=\"future\")\n        mock_load_x509.return_value = [cert_mock]\n        self.hooks.report_successes = True\n        self.hooks._done = MagicMock()\n        self.hooks._attach_csr = MagicMock()\n        self.hooks._attach_cert = MagicMock()\n        self.hooks.envelope = {\"Subject\": None}\n        self.hooks.msg = []\n        # This should not raise, but should log an error\n        with self.assertLogs(self.logger, level=\"DEBUG\") as cm:\n            self.hooks.success_hook(\n                \"reqkey\", \"order\", \"csr\", \"cert\", \"cert_raw\", \"pollid\"\n            )\n        self.assertTrue(\n            any(\n                \"Falling back to not_valid_before and not_valid_after for certificate dates\"\n                in msg\n                for msg in cm.output\n            )\n        )\n\n    def test_044_pre_hook(self):\n        \"\"\"pre_hook handles missing CSR gracefully\"\"\"\n        with self.assertLogs(self.logger, level=\"DEBUG\") as cm:\n            self.hooks.pre_hook(\"reqkey\", \"order\", None)\n        self.assertTrue(any(\"called - no action required\" in msg for msg in cm.output))\n\n    @patch(\"examples.hooks.email_hooks.smtplib.SMTP_SSL\")\n    def test_045_done_sends_email_with_tls(self, mock_smtp_ssl):\n        \"\"\"_done sends email using SMTP_SSL when smtp_use_tls is True\"\"\"\n        self.hooks.smtp_use_tls = True\n        self.hooks.smtp_use_starttls = False\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 465\n        self.hooks.smtp_timeout = 10\n        self.hooks.smtp_username = \"user\"\n        self.hooks.smtp_password = \"pass\"\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        # Create a mock SMTP instance\n        smtp_instance = MagicMock()\n        mock_smtp_ssl.return_value.__enter__.return_value = smtp_instance\n\n        # Call the method\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._done()\n\n        # Verify SMTP_SSL was called with correct parameters\n        mock_smtp_ssl.assert_called_with(\"smtp.example.com\", 465, timeout=10)\n\n        # Verify done flag is set and success is logged\n        self.assertTrue(self.hooks.done)\n        log_output = \"\\n\".join(cm.output)\n        self.assertIn(\"Email notification sent successfully\", log_output)\n\n    @patch(\"examples.hooks.email_hooks.smtplib.SMTP\")\n    def test_046_done_sends_email_with_starttls(self, mock_smtp):\n        \"\"\"_done sends email using SMTP with STARTTLS when smtp_use_starttls is True\"\"\"\n        self.hooks.smtp_use_tls = False\n        self.hooks.smtp_use_starttls = True\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 587\n        self.hooks.smtp_timeout = 10\n        self.hooks.smtp_username = \"user\"\n        self.hooks.smtp_password = \"pass\"\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        smtp_instance = MagicMock()\n        mock_smtp.return_value.__enter__.return_value = smtp_instance\n\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._done()\n\n        mock_smtp.assert_called_with(\"smtp.example.com\", 587, timeout=10)\n        self.assertTrue(self.hooks.done)\n        log_output = \"\\n\".join(cm.output)\n        self.assertIn(\"Email notification sent successfully\", log_output)\n\n    @patch(\"examples.hooks.email_hooks.smtplib.SMTP\")\n    def test_047_done_sends_email_without_auth(self, mock_smtp):\n        \"\"\"_done sends email without authentication when no credentials provided\"\"\"\n        self.hooks.smtp_use_tls = False\n        self.hooks.smtp_use_starttls = False\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 25\n        self.hooks.smtp_timeout = 30\n        self.hooks.smtp_username = None\n        self.hooks.smtp_password = None\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        smtp_instance = MagicMock()\n        mock_smtp.return_value.__enter__.return_value = smtp_instance\n\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._done()\n\n        mock_smtp.assert_called_with(\"smtp.example.com\", 25, timeout=30)\n        self.assertTrue(self.hooks.done)\n        log_output = \"\\n\".join(cm.output)\n        self.assertIn(\"Email notification sent successfully\", log_output)\n\n    @patch(\"examples.hooks.email_hooks.smtplib.SMTP\")\n    def test_048_done_logs_debug_info(self, mock_smtp):\n        \"\"\"_done logs detailed debug information about SMTP connection\"\"\"\n        self.hooks.smtp_use_tls = False\n        self.hooks.smtp_use_starttls = False\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 25\n        self.hooks.smtp_timeout = 30\n        self.hooks.smtp_username = \"testuser\"\n        self.hooks.smtp_password = \"testpass\"\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        smtp_instance = MagicMock()\n        mock_smtp.return_value.__enter__.return_value = smtp_instance\n\n        with self.assertLogs(self.logger, level=\"DEBUG\") as cm:\n            self.hooks._done()\n\n        log_output = \"\\n\".join(cm.output)\n        self.assertIn(\"Attempting to send email notification\", log_output)\n        self.assertIn(\"TLS settings\", log_output)\n        self.assertIn(\"Authentication - username: testuser\", log_output)\n        self.assertIn(\"password: ***\", log_output)\n        self.assertTrue(self.hooks.done)\n\n    @patch(\n        \"examples.hooks.email_hooks.smtplib.SMTP_SSL\",\n        side_effect=Exception(\"Connection failed\"),\n    )\n    def test_049_done_handles_smtp_connection_error(self, mock_smtp_ssl):\n        \"\"\"_done handles SMTP connection errors gracefully\"\"\"\n        self.hooks.smtp_use_tls = True\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 465\n        self.hooks.smtp_timeout = 10\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks._done()\n\n        self.assertTrue(any(\"Email sending failed\" in msg for msg in cm.output))\n        self.assertTrue(any(\"Connection failed\" in msg for msg in cm.output))\n        self.assertTrue(self.hooks.done)  # Still sets done=True even on error\n\n    @patch(\n        \"examples.hooks.email_hooks.smtplib.SMTP\",\n        side_effect=Exception(\"Connection failed\"),\n    )\n    def test_050_done_handles_smtp_auth_error(self, mock_smtp):\n        \"\"\"_done handles SMTP connection errors gracefully\"\"\"\n        self.hooks.smtp_use_tls = False\n        self.hooks.smtp_use_starttls = False\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 25\n        self.hooks.smtp_timeout = 30\n        self.hooks.smtp_username = \"user\"\n        self.hooks.smtp_password = \"wrongpass\"\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        with self.assertLogs(self.logger, level=\"ERROR\") as cm:\n            self.hooks._done()\n\n        self.assertTrue(any(\"Email sending failed\" in msg for msg in cm.output))\n        self.assertTrue(any(\"Connection failed\" in msg for msg in cm.output))\n        self.assertTrue(self.hooks.done)\n\n    @patch(\"examples.hooks.email_hooks.smtplib.SMTP\")\n    def test_051_done_logs_success_info(self, mock_smtp):\n        \"\"\"_done logs success information when email is sent\"\"\"\n        self.hooks.smtp_use_tls = False\n        self.hooks.smtp_use_starttls = False\n        self.hooks.smtp_server = \"smtp.example.com\"\n        self.hooks.smtp_port = 25\n        self.hooks.smtp_timeout = 30\n        self.hooks.sender = \"sender@example.com\"\n        self.hooks.rcpt = \"rcpt@example.com\"\n        self.hooks.envelope[\"Subject\"] = \"Test Subject\"\n        self.hooks.msg = [\"Test message\"]\n\n        smtp_instance = MagicMock()\n        mock_smtp.return_value.__enter__.return_value = smtp_instance\n\n        with self.assertLogs(self.logger, level=\"INFO\") as cm:\n            self.hooks._done()\n\n        log_output = \"\\n\".join(cm.output)\n        self.assertIn(\"Email notification sent successfully\", log_output)\n        self.assertIn(\"rcpt@example.com\", log_output)\n        self.assertIn(\"Subject: Test Subject\", log_output)\n        self.assertTrue(self.hooks.done)\n\n    def test_052_done_already_sent_warning(self):\n        \"\"\"_done warns when called multiple times\"\"\"\n        self.hooks.done = True\n\n        with self.assertLogs(self.logger, level=\"WARNING\") as cm:\n            self.hooks._done()\n\n        self.assertIn(\"email already sent\", \"\\n\".join(cm.output))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_entrust.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openxpki_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, mock_open, Mock, MagicMock\nimport requests\nimport base64\nfrom OpenSSL import crypto\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n        from examples.ca_handler.entrust_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_load\")\n    def test_003__enter__(self, mock_cfg):\n        \"\"\"test enter api hosts defined\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.session = \"session\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    def test_004_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_005_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_006__api_post(self):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_post(\"url\", \"data\")\n        )\n\n    def test_007__api_post(self):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json.side_effect = Exception(\"ex_json\")\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"ex_json\"), self.cahandler._api_post(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: ex_json\",\n            lcm.output,\n        )\n\n    def test_008__api_post(self):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = None\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_post(\"url\", \"data\"))\n\n    def test_009__api_post(self):\n        \"\"\"test _api_post(=\"\"\"\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [Exception(\"exc_api_post\")]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_post\"), self.cahandler._api_post(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_post\", lcm.output\n        )\n\n    def test_010__api_get(self):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.get.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_get(\"url\")\n        )\n\n    def test_011__api_get(self):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json.side_effect = Exception(\"ex_json\")\n        mockresponse = Mock()\n        mockresponse.get.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"status_code\", \"ex_json\"), self.cahandler._api_get(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: ex_json\",\n            lcm.output,\n        )\n\n    def test_012__api_get(self):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.get.side_effect = [Exception(\"exc_api_get\")]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((500, \"exc_api_get\"), self.cahandler._api_get(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_get\", lcm.output\n        )\n\n    def test_013__api_put(self):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.put.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_put(\"url\", \"data\")\n        )\n\n    def test_014__api_put(self):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"status_code\"\n        mockresponse2.text = \"foo\"\n        mockresponse2.json.side_effect = Exception(\"ex_json\")\n        mockresponse = Mock()\n        mockresponse.put.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"ex_json\"), self.cahandler._api_put(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: ex_json\",\n            lcm.output,\n        )\n\n    def test_015__api_put(self):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"foo\"\n        mockresponse2.text = None\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.put.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.assertEqual((\"foo\", None), self.cahandler._api_put(\"url\", \"data\"))\n\n    def test_016__api_put(self):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.put.side_effect = Exception(\"exc_api_put\")\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_put\"), self.cahandler._api_put(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_put\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_017_certificates_get_from_serial(self, mock_api):\n        \"\"\"test certificates_get_from_serial\"\"\"\n        mock_api.return_value = (200, {\"certificates\": [\"foo\", \"bar\"]})\n        self.assertEqual(\n            [\"foo\", \"bar\"], self.cahandler._certificates_get_from_serial(\"serial\")\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_018_certificates_get_from_serial(self, mock_api):\n        \"\"\"test certificates_get_from_serial\"\"\"\n        mock_api.return_value = (300, {\"certificates\": [\"foo\", \"bar\"]})\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._certificates_get_from_serial(\"serial\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate lookup based on serial number failed for serial with code: 300\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_019_certificates_get_from_serial(self, mock_api):\n        \"\"\"test certificates_get_from_serial\"\"\"\n        mock_api.return_value = (200, {\"certificates1\": [\"foo\", \"bar\"]})\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._certificates_get_from_serial(\"serial\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate lookup based on serial number failed for serial with code: 200\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_020_certificates_get_from_serial(self, mock_api):\n        \"\"\"test certificates_get_from_serial\"\"\"\n        mock_api.return_value = (200, {\"certificates\": [\"foo\", \"bar\"]})\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                [\"foo\", \"bar\"], self.cahandler._certificates_get_from_serial(\"0serial\")\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Remove leading zeros from serial number\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_021_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['CAhandler'] = {'foo': 'bar'}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertFalse(mock_session.called)\n        self.assertFalse(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_022_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_023_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_url\": \"api_url\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\"api_url\", self.cahandler.api_url)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_024_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"15\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(15, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_025_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse request_timeout parameter: invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_026_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_validity_days\": \"10\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(10, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_027_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_validity_days\": \"aa\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse cert_validity_days invalid literal for int() with base 10: 'aa' parameter\",\n            lcm.output,\n        )\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_028_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"username\": \"username\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertEqual(\"username\", self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_029_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password\": \"password\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertEqual(\"password\", self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_030_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"organization_name\": \"organization_name\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"organization_name\", self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_031_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"certtype\": \"certtype\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"certtype\", self.cahandler.certtype)\n        self.assertFalse(self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_032_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"foo\", \"bar\"]', \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertEqual([\"foo\", \"bar\"], self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_headerinfo_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_root_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.load_config\")\n    def test_033_config_load(\n        self, mock_load, mock_session, mock_root, mock_eab, mock_header\n    ):\n        \"\"\"test load_config()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": \"bar\", \"foo\": \"bar\"}\n        mock_load.return_value = parser\n        mock_eab.return_value = (True, \"handler\")\n        mock_header.return_value = \"hil\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_session.called)\n        self.assertTrue(mock_root.called)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_header.called)\n        self.assertEqual(\n            \"https://api.entrust.net/enterprise/v2\", self.cahandler.api_url\n        )\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertTrue(self.cahandler.eab_profiling)\n        self.assertEqual(365, self.cahandler.cert_validity_days)\n        self.assertFalse(self.cahandler.username)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.organization_name)\n        self.assertEqual(\"STANDARD_SSL\", self.cahandler.certtype)\n        self.assertEqual(\"failed to parse\", self.cahandler.allowed_domainlist)\n        self.assertEqual(\"hil\", self.cahandler.header_info_field)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_034_config_passphrase_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_var\"}\n        self.cahandler._config_passphrase_load(parser)\n        self.assertEqual(\"user_var\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_035_config_passphrase_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"does_not_exist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_passphrase_load(parser)\n        self.assertFalse(self.cahandler.cert_passphrase)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cert_passphrase_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_var\": \"user_var\"})\n    def test_036_config_passphrase_load(self):\n        \"\"\"test _config_load - load template with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_passphrase_variable\": \"cert_passphrase_var\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_passphrase_load(parser)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite cert_passphrase\",\n            lcm.output,\n        )\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cert\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_037_config_root_load(self, mock_file):\n        \"\"\"_config_root_load()\"\"\"\n        mock_file.return_value = True\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"entrust_root_cert\": \"root_cert\"}\n        self.cahandler._config_root_load(parser)\n        self.assertEqual(\"cert\", self.cahandler.entrust_root_cert)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cert\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_038_config_root_load(self, mock_file):\n        \"\"\"_config_root_load()\"\"\"\n        mock_file.return_value = False\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"entrust_root_cert\": \"root_cert\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_root_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Root CA file configured but not not found. Using default one.\",\n            lcm.output,\n        )\n        self.assertIn(\"290IENlcnRpZmljYXRpb24g\", self.cahandler.entrust_root_cert)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cert\"), create=True)\n    @patch(\"os.path.isfile\")\n    def test_039_config_root_load(self, mock_file):\n        \"\"\"_config_root_load()\"\"\"\n        mock_file.return_value = False\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"unk\": \"root_cert\"}\n        self.cahandler._config_root_load(parser)\n        self.assertIn(\"290IENlcnRpZmljYXRpb24g\", self.cahandler.entrust_root_cert)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_passphrase_load\")\n    def test_040_config_session_load(self, mock_sl):\n        \"\"\"_config_session_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"client_cert\": \"client_cert\", \"client_key\": \"client_key\"}\n        self.cahandler._config_session_load(parser)\n        self.assertFalse(mock_sl.called)\n        self.assertTrue(self.cahandler.session)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.requests.Session\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.Pkcs12Adapter\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_passphrase_load\")\n    def test_041_config_session_load(self, mock_sl, mock_pkcs12, mock_session):\n        \"\"\"_config_session_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"client_cert\": \"client_cert\"}\n        mock_session.return_value.__enter__.return_value = Mock()\n        self.cahandler.cert_passphrase = \"cert_passphrase\"\n        self.cahandler._config_session_load(parser)\n        self.assertTrue(mock_sl.called)\n        self.assertTrue(self.cahandler.session)\n        self.assertTrue(mock_pkcs12.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.requests.Session\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.Pkcs12Adapter\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_passphrase_load\")\n    def test_042_config_session_load(self, mock_sl, mock_pkcs12, mock_session):\n        \"\"\"_config_session_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"client_cert\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertTrue(mock_sl.called)\n        self.assertTrue(self.cahandler.session)\n        self.assertIn(\n            'WARNING:test_a2c:Configuration might be incomplete: \"client_cert\", \"client_key\" or \"client_passphrase[_variable]\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertFalse(mock_pkcs12.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._domains_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._organizations_get\")\n    def test_043_org_domain_cfg_check(self, mock_org, mock_domain):\n        \"\"\"test _org_domain_cfg_check()\"\"\"\n        mock_org.return_value = []\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"Organization None not found in Entrust API\",\n                self.cahandler._org_domain_cfg_check(),\n            )\n        self.assertTrue(mock_org.called)\n        self.assertFalse(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._domains_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._organizations_get\")\n    def test_044_org_domain_cfg_check(self, mock_org, mock_domain):\n        \"\"\"test _org_domain_cfg_check()\"\"\"\n        mock_org.return_value = {\"foo\": 1, \"bar\": 2}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"Organization None not found in Entrust API\",\n                self.cahandler._org_domain_cfg_check(),\n            )\n        self.assertTrue(mock_org.called)\n        self.assertFalse(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._domains_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._organizations_get\")\n    def test_045_org_domain_cfg_check(self, mock_org, mock_domain):\n        \"\"\"test _org_domain_cfg_check()\"\"\"\n        mock_org.return_value = {\"foo\": 1, \"bar\": 2}\n        mock_domain.return_value = [\"foo1\", \"foo2\"]\n        self.cahandler.organization_name = \"foo1\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"Organization foo1 not found in Entrust API\",\n                self.cahandler._org_domain_cfg_check(),\n            )\n        self.assertTrue(mock_org.called)\n        self.assertFalse(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._domains_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._organizations_get\")\n    def test_046_org_domain_cfg_check(self, mock_org, mock_domain):\n        \"\"\"test _org_domain_cfg_check()\"\"\"\n        mock_org.return_value = {\"foo\": 1, \"bar\": 2}\n        mock_domain.return_value = [\"foo1\", \"foo2\"]\n        self.cahandler.organization_name = \"foo\"\n        self.assertFalse(self.cahandler._org_domain_cfg_check())\n        self.assertTrue(mock_org.called)\n        self.assertTrue(mock_domain.called)\n        self.assertEqual([\"foo1\", \"foo2\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._domains_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._organizations_get\")\n    def test_047_org_domain_cfg_check(self, mock_org, mock_domain):\n        \"\"\"test _org_domain_cfg_check()\"\"\"\n        mock_org.return_value = {\"foo\": 1, \"bar\": 2}\n        mock_domain.return_value = [\"foo1\", \"foo2\"]\n        self.cahandler.organization_name = \"foo\"\n        self.cahandler.allowed_domainlist = [\"foo3\", \"foo4\"]\n        self.assertFalse(self.cahandler._org_domain_cfg_check())\n        self.assertTrue(mock_org.called)\n        self.assertTrue(mock_domain.called)\n        self.assertEqual([\"foo3\", \"foo4\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_048__organizations_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        mock_api.return_value = (500, \"foo\")\n        self.cahandler.organization_name = \"organization_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._organizations_get()\n        self.assertIn(\n            \"ERROR:test_a2c:Malformed response while getting the organization list from API\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_049__organizations_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"organizations\": [\n                {\"verificationStatus\": \"APPROVED\", \"name\": \"foo\", \"clientId\": 1},\n                {\"verificationStatus\": \"APPROVED\", \"name\": \"bar\", \"clientId\": 2},\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertEqual({\"foo\": 1, \"bar\": 2}, self.cahandler._organizations_get())\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_050__organizations_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"organizations\": [\n                {\"verificationStatus\": \"NOTAPPROVED\", \"name\": \"foo\", \"clientId\": 1},\n                {\"verificationStatus\": \"APPROVED\", \"name\": \"bar\", \"clientId\": 2},\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertEqual({\"bar\": 2}, self.cahandler._organizations_get())\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_051__organizations_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"organizations\": [\n                {\"name\": \"foo\", \"clientId\": 1},\n                {\"verificationStatus\": \"APPROVED\", \"name\": \"bar\", \"clientId\": 2},\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertEqual({\"bar\": 2}, self.cahandler._organizations_get())\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_052__organizations_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"organizations\": [\n                {\"verificationStatus\": \"APPROVED\", \"_name\": \"foo\", \"_clientId\": 1},\n                {\"verificationStatus\": \"APPROVED\", \"name\": \"bar\", \"clientId\": 2},\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertEqual({\"bar\": 2}, self.cahandler._organizations_get())\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_053__domains_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        mock_api.return_value = (500, \"foo\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._domains_get(1)\n        self.assertIn(\n            \"ERROR:test_a2c:Malformed response while getting the domain list from API\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_054__domains_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"domains\": [\n                {\n                    \"verificationStatus\": \"APPROVED\",\n                    \"domainName\": \"foo.bar\",\n                    \"clientId\": 1,\n                },\n                {\n                    \"verificationStatus\": \"APPROVED\",\n                    \"domainName\": \"bar.foo\",\n                    \"clientId\": 2,\n                },\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.assertEqual([\"foo.bar\", \"bar.foo\"], self.cahandler._domains_get(1))\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_055__domains_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"domains\": [\n                {\n                    \"verificationStatus\": \"NOTAPPROVED\",\n                    \"domainName\": \"foo.bar\",\n                    \"clientId\": 1,\n                },\n                {\n                    \"verificationStatus\": \"APPROVED\",\n                    \"domainName\": \"bar.foo\",\n                    \"clientId\": 2,\n                },\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.assertEqual([\"bar.foo\"], self.cahandler._domains_get(1))\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_056__domains_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"domains\": [\n                {\"Status\": \"APPROVED\", \"domainName\": \"foo.bar\", \"clientId\": 1},\n                {\n                    \"verificationStatus\": \"APPROVED\",\n                    \"domainName\": \"bar.foo\",\n                    \"clientId\": 2,\n                },\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.assertEqual([\"bar.foo\"], self.cahandler._domains_get(1))\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_057__domains_get(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        input_dic = {\n            \"domains\": [\n                {\"verificationStatus\": \"APPROVED\", \"Name\": \"foo.bar\", \"clientId\": 1},\n                {\n                    \"verificationStatus\": \"APPROVED\",\n                    \"domainName\": \"bar.foo\",\n                    \"clientId\": 2,\n                },\n            ]\n        }\n        mock_api.return_value = (200, input_dic)\n        self.assertEqual([\"bar.foo\"], self.cahandler._domains_get(1))\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_058_credential_check(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        mock_api.return_value = (500, \"foo\")\n        self.assertEqual(\n            \"Connection to Entrust API failed: foo\", self.cahandler.credential_check()\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_059_credential_check(self, mock_api):\n        \"\"\"test _organizations_get()\"\"\"\n        mock_api.return_value = (200, \"foo\")\n        self.assertFalse(self.cahandler.credential_check())\n\n    def test_060_oonfig_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: api_url parameter in missing in config file\",\n            lcm.output,\n        )\n\n    def test_061_oonfig_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: username parameter in missing in config file\",\n            lcm.output,\n        )\n\n    def test_062_oonfig_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.cahandler.username = \"username\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: password parameter in missing in config file\",\n            lcm.output,\n        )\n\n    def test_063_oonfig_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.cahandler.username = \"username\"\n        self.cahandler.password = \"password\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: organization_name parameter in missing in config file\",\n            lcm.output,\n        )\n\n    def test_064_oonfig_check(self):\n        \"\"\"test _config_check()\"\"\"\n        self.cahandler.api_url = \"api_url\"\n        self.cahandler.username = \"username\"\n        self.cahandler.password = \"password\"\n        self.cahandler.organization_name = \"organization_name\"\n        self.assertFalse(self.cahandler._config_check())\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._org_domain_cfg_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler.credential_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_header_info_check\")\n    def test_065_enroll_check(\n        self, mock_eab, mock_config, mock_credential, mock_org, mock_domain\n    ):\n        \"\"\"test _enroll_check()\"\"\"\n        mock_eab.return_value = \"mock_eab_error\"\n        mock_config.return_value = \"mock_config_error\"\n        mock_credential.return_value = \"mock_credential_error\"\n        mock_org.return_value = \"mock_org_error\"\n        mock_domain.return_value = \"mock_domain_error\"\n        self.assertEqual(\"mock_eab_error\", self.cahandler._enroll_check(\"csr\"))\n        self.assertTrue(mock_eab.called)\n        self.assertFalse(mock_config.called)\n        self.assertFalse(mock_credential.called)\n        self.assertFalse(mock_org.called)\n        self.assertFalse(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._org_domain_cfg_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler.credential_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_header_info_check\")\n    def test_066_enroll_check(\n        self, mock_eab, mock_config, mock_credential, mock_org, mock_domain\n    ):\n        \"\"\"test _enroll_check()\"\"\"\n        mock_eab.return_value = False\n        mock_config.return_value = \"mock_config_error\"\n        mock_credential.return_value = \"mock_credential_error\"\n        mock_org.return_value = \"mock_org_error\"\n        mock_domain.return_value = \"mock_domain_error\"\n        self.assertEqual(\"mock_config_error\", self.cahandler._enroll_check(\"csr\"))\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_config.called)\n        self.assertFalse(mock_credential.called)\n        self.assertFalse(mock_org.called)\n        self.assertFalse(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._org_domain_cfg_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler.credential_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_header_info_check\")\n    def test_067_enroll_check(\n        self, mock_eab, mock_config, mock_credential, mock_org, mock_domain\n    ):\n        \"\"\"test _enroll_check()\"\"\"\n        mock_eab.return_value = False\n        mock_config.return_value = False\n        mock_credential.return_value = \"mock_credential_error\"\n        mock_org.return_value = False\n        mock_domain.return_value = False\n        self.assertEqual(\"mock_credential_error\", self.cahandler._enroll_check(\"csr\"))\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_config.called)\n        self.assertTrue(mock_credential.called)\n        self.assertTrue(mock_org.called)\n        self.assertTrue(mock_domain.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._org_domain_cfg_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler.credential_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_header_info_check\")\n    def test_068_enroll_check(\n        self, mock_eab, mock_config, mock_credential, mock_org, mock_domain\n    ):\n        \"\"\"test _enroll_check()\"\"\"\n        mock_eab.return_value = False\n        mock_config.return_value = False\n        mock_credential.return_value = False\n        mock_org.return_value = \"mock_org_error\"\n        mock_domain.return_value = \"mock_domain_error\"\n        self.assertEqual(\"mock_org_error\", self.cahandler._enroll_check(\"csr\"))\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_config.called)\n        self.assertTrue(mock_org.called)\n        self.assertFalse(mock_domain.called)\n        self.assertFalse(mock_credential.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.allowed_domainlist_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._org_domain_cfg_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler.credential_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_header_info_check\")\n    def test_069_enroll_check(\n        self, mock_eab, mock_config, mock_credential, mock_org, mock_domain\n    ):\n        \"\"\"test _enroll_check()\"\"\"\n        mock_eab.return_value = False\n        mock_config.return_value = False\n        mock_credential.return_value = False\n        mock_org.return_value = False\n        mock_domain.return_value = \"mock_domain_error\"\n        self.assertEqual(\"mock_domain_error\", self.cahandler._enroll_check(\"csr\"))\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(mock_org.called)\n        self.assertTrue(mock_domain.called)\n        self.assertTrue(mock_config.called)\n        self.assertFalse(mock_credential.called)\n\n    @patch(\n        \"examples.ca_handler.entrust_ca_handler.CAhandler._certificates_get_from_serial\"\n    )\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.header_info_get\")\n    def test_070__trackingid_get(self, mock_header, mock_serial, mock_cert):\n        \"\"\"test _trackingid_get()\"\"\"\n        mock_header.return_value = [\n            {\"poll_identifier\": \"tracking_id\"},\n            {\"poll_identifier\": \"tracking_id2\"},\n        ]\n        self.assertEqual(\"tracking_id\", self.cahandler._trackingid_get(\"csr\"))\n        self.assertFalse(mock_serial.called)\n        self.assertFalse(mock_cert.called)\n\n    @patch(\n        \"examples.ca_handler.entrust_ca_handler.CAhandler._certificates_get_from_serial\"\n    )\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.header_info_get\")\n    def test_071__trackingid_get(self, mock_header, mock_serial, mock_cert):\n        \"\"\"test _trackingid_get()\"\"\"\n        mock_header.return_value = [\n            {\"identifier\": \"tracking_id1\"},\n            {\"poll_identifier\": \"tracking_id2\"},\n        ]\n        self.assertEqual(\"tracking_id2\", self.cahandler._trackingid_get(\"csr\"))\n        self.assertFalse(mock_serial.called)\n        self.assertFalse(mock_cert.called)\n\n    @patch(\n        \"examples.ca_handler.entrust_ca_handler.CAhandler._certificates_get_from_serial\"\n    )\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.header_info_get\")\n    def test_072__trackingid_get(self, mock_header, mock_serial, mock_cert):\n        \"\"\"test _trackingid_get()\"\"\"\n        mock_header.return_value = []\n        mock_serial.return_value = \"serial\"\n        mock_cert.return_value = [{\"trackingId\": \"tracking_id\"}]\n        self.assertEqual(\"tracking_id\", self.cahandler._trackingid_get(\"csr\"))\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_cert.called)\n\n    @patch(\n        \"examples.ca_handler.entrust_ca_handler.CAhandler._certificates_get_from_serial\"\n    )\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.header_info_get\")\n    def test_073__trackingid_get(self, mock_header, mock_serial, mock_cert):\n        \"\"\"test _trackingid_get()\"\"\"\n        mock_header.return_value = []\n        mock_serial.return_value = \"serial\"\n        mock_cert.return_value = [{\"id\": \"tracking_id\"}]\n        self.assertFalse(self.cahandler._trackingid_get(\"csr\"))\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_cert.called)\n\n    @patch(\n        \"examples.ca_handler.entrust_ca_handler.CAhandler._certificates_get_from_serial\"\n    )\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.header_info_get\")\n    def test_074__trackingid_get(self, mock_header, mock_serial, mock_cert):\n        \"\"\"test _trackingid_get()\"\"\"\n        mock_header.return_value = []\n        mock_serial.return_value = \"serial\"\n        mock_cert.return_value = [\n            {\"id\": \"tracking_id1\"},\n            {\"trackingId\": \"tracking_id2\"},\n        ]\n        self.assertEqual(\"tracking_id2\", self.cahandler._trackingid_get(\"csr\"))\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_cert.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_pem2der\")\n    def test_075_response_parse(self, mock_der, mock_enc):\n        \"\"\"test _rsponse_parse()\"\"\"\n        mock_der.return_value = \"cert_data\"\n        mock_enc.return_value = \"mock_enc\"\n        self.cahandler.entrust_root_cert = \"root_cert\"\n        response = {\n            \"trackingId\": \"trackingId\",\n            \"endEntityCert\": \"endEntityCert\",\n            \"chainCerts\": [\"foo1\", \"foo2\"],\n        }\n        self.assertEqual(\n            (\"foo1\\nfoo2\\nroot_cert\\n\", \"mock_enc\", \"trackingId\"),\n            self.cahandler._response_parse(response),\n        )\n        self.assertTrue(mock_der.called)\n        self.assertTrue(mock_enc.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_pem2der\")\n    def test_076_response_parse(self, mock_der, mock_enc):\n        \"\"\"test _rsponse_parse()\"\"\"\n        mock_der.return_value = \"cert_data\"\n        mock_enc.return_value = \"mock_enc\"\n        self.cahandler.entrust_root_cert = \"root_cert\"\n        response = {\n            \"falsetrackingId\": \"trackingId\",\n            \"endEntityCert\": \"endEntityCert\",\n            \"chainCerts\": [\"foo1\", \"foo2\"],\n        }\n        self.assertEqual(\n            (\"foo1\\nfoo2\\nroot_cert\\n\", \"mock_enc\", None),\n            self.cahandler._response_parse(response),\n        )\n        self.assertTrue(mock_der.called)\n        self.assertTrue(mock_enc.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_pem2der\")\n    def test_077_response_parse(self, mock_der, mock_enc):\n        \"\"\"test _rsponse_parse()\"\"\"\n        mock_der.return_value = \"cert_data\"\n        mock_enc.return_value = \"mock_enc\"\n        self.cahandler.entrust_root_cert = \"root_cert\"\n        response = {\n            \"trackingId\": \"trackingId\",\n            \"endEntityCert\": \"endEntityCert\",\n            \"Certs\": [\"foo1\", \"foo2\"],\n        }\n        self.assertEqual(\n            (None, None, \"trackingId\"), self.cahandler._response_parse(response)\n        )\n        self.assertFalse(mock_der.called)\n        self.assertFalse(mock_enc.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_pem2der\")\n    def test_078_response_parse(self, mock_der, mock_enc):\n        \"\"\"test _rsponse_parse()\"\"\"\n        mock_der.return_value = \"cert_data\"\n        mock_enc.return_value = \"mock_enc\"\n        self.cahandler.entrust_root_cert = \"root_cert\"\n        response = {\n            \"trackingId\": \"trackingId\",\n            \"EntityCert\": \"endEntityCert\",\n            \"chainCerts\": [\"foo1\", \"foo2\"],\n        }\n        self.assertEqual(\n            (None, None, \"trackingId\"), self.cahandler._response_parse(response)\n        )\n        self.assertFalse(mock_der.called)\n        self.assertFalse(mock_enc.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.cert_pem2der\")\n    def test_079_response_parse(self, mock_der, mock_enc):\n        \"\"\"test _rsponse_parse()\"\"\"\n        mock_der.return_value = \"cert_data\"\n        mock_enc.return_value = \"mock_enc\"\n        self.cahandler.entrust_root_cert = \"root_cert\"\n        response = {\n            \"trackingId\": \"trackingId\",\n            \"endEntityCert\": \"endEntityCert\",\n            \"chainCerts\": [],\n        }\n        self.assertEqual(\n            (\"root_cert\\n\", \"mock_enc\", \"trackingId\"),\n            self.cahandler._response_parse(response),\n        )\n        self.assertTrue(mock_der.called)\n        self.assertTrue(mock_enc.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._response_parse\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    def test_080_enroll(self, mock_req, mock_cn, mock_parse, mock_ecl):\n        \"\"\"test _enroll()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_req.return_value = (201, \"response\")\n        mock_parse.return_value = (\"cert_bundle\", \"cert_raw\", \"poll_indentifier\")\n        self.assertEqual(\n            (None, \"cert_bundle\", \"cert_raw\", \"poll_indentifier\"),\n            self.cahandler._enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cn.called)\n        self.assertTrue(mock_req.called)\n        self.assertTrue(mock_parse.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._response_parse\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    def test_081_enroll(self, mock_req, mock_cn, mock_parse):\n        \"\"\"test _enroll()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_req.return_value = (404, \"response\")\n        mock_parse.return_value = (\"cert_bundle\", \"cert_raw\", \"poll_indentifier\")\n        self.assertEqual(\n            (\"Error during order creation: 404 - response\", None, None, None),\n            self.cahandler._enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cn.called)\n        self.assertTrue(mock_req.called)\n        self.assertFalse(mock_parse.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._response_parse\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    def test_082_enroll(self, mock_req, mock_cn, mock_parse):\n        \"\"\"test _enroll()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_req.return_value = (404, {\"errors\": \"response, response2\"})\n        mock_parse.return_value = (\"cert_bundle\", \"cert_raw\", \"poll_indentifier\")\n        self.assertEqual(\n            (\n                \"Error during order creation: 404 - response, response2\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler._enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cn.called)\n        self.assertTrue(mock_req.called)\n        self.assertFalse(mock_parse.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._response_parse\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.csr_cn_lookup\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    def test_083_enroll(self, mock_req, mock_cn, mock_parse, mock_ecl):\n        \"\"\"test _enroll()\"\"\"\n        mock_cn.return_value = \"cn\"\n        mock_req.return_value = (201, \"response\")\n        mock_parse.return_value = (\"cert_bundle\", \"cert_raw\", \"poll_indentifier\")\n        self.cahandler.enrollment_config_log = True\n        self.assertEqual(\n            (None, \"cert_bundle\", \"cert_raw\", \"poll_indentifier\"),\n            self.cahandler._enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cn.called)\n        self.assertTrue(mock_req.called)\n        self.assertTrue(mock_parse.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._enroll_check\")\n    def test_084_enroll(self, mock_chk, mock_enroll):\n        \"\"\"test enroll()\"\"\"\n        mock_chk.return_value = None\n        mock_enroll.return_value = (\"mock_err\", \"mock_bundle\", \"mock_raw\", \"mock_poll\")\n        self.assertEqual(\n            (\"mock_err\", \"mock_bundle\", \"mock_raw\", \"mock_poll\"),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._enroll_check\")\n    def test_085_enroll(self, mock_chk, mock_enroll):\n        \"\"\"test enroll()\"\"\"\n        mock_chk.return_value = \"mock_chk\"\n        mock_enroll.return_value = (\"mock_err\", \"mock_bundle\", \"mock_raw\", \"mock_poll\")\n        self.assertEqual((\"mock_chk\", None, None, None), self.cahandler.enroll(\"csr\"))\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._trackingid_get\")\n    def test_086_revoke(self, mock_track, mock_req, mock_eab):\n        \"\"\"test revoke()\"\"\"\n        mock_track.return_value = \"tracking_id\"\n        mock_req.return_value = (200, \"response\")\n        self.assertEqual(\n            (200, \"Certificate revoked\", None), self.cahandler.revoke(\"csr\")\n        )\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._trackingid_get\")\n    def test_087_revoke(self, mock_track, mock_req, mock_eab):\n        \"\"\"test revoke()\"\"\"\n        mock_track.return_value = \"tracking_id\"\n        mock_req.return_value = (200, \"response\")\n        self.cahandler.eab_profiling = True\n        self.assertEqual(\n            (200, \"Certificate revoked\", None), self.cahandler.revoke(\"csr\")\n        )\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._trackingid_get\")\n    def test_087_revoke(self, mock_track, mock_req):\n        \"\"\"test revoke()\"\"\"\n        mock_track.return_value = \"tracking_id\"\n        mock_req.return_value = (500, \"response\")\n        self.assertEqual(\n            (500, \"urn:ietf:params:acme:error:serverInternal\", \"response\"),\n            self.cahandler.revoke(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._trackingid_get\")\n    def test_088_revoke(self, mock_track, mock_req):\n        \"\"\"test revoke()\"\"\"\n        mock_track.return_value = None\n        mock_req.return_value = (200, \"response\")\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Failed to get tracking id\",\n            ),\n            self.cahandler.revoke(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_089_certificates_get(self, mock_req):\n        \"\"\"test certificates_get()\"\"\"\n        mock_req.return_value = (500, \"response\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler.certificates_get())\n        self.assertIn(\n            \"ERROR:test_a2c:Getting certificate data failed with code: 500\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_090_certificates_get(self, mock_req):\n        \"\"\"test certificates_get()\"\"\"\n        content = {\"certificates\": [1, 2, 3, 4], \"summary\": {\"total\": 4}}\n        mock_req.return_value = (200, content)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual([1, 2, 3, 4], self.cahandler.certificates_get())\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 0, limit: 200, total: 1, buffered: 0\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_091_certificates_get(self, mock_req):\n        \"\"\"test certificates_get()\"\"\"\n        response1 = (200, {\"certificates\": [1, 2, 3, 4], \"summary\": {\"total\": 8}})\n        response2 = (200, {\"certificates\": [5, 6, 7, 8]})\n        mock_req.side_effect = [response1, response2]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                [1, 2, 3, 4, 5, 6, 7, 8], self.cahandler.certificates_get()\n            )\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 0, limit: 200, total: 1, buffered: 0\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 200, limit: 200, total: 8, buffered: 4\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_092_certificates_get(self, mock_req):\n        \"\"\"test certificates_get()\"\"\"\n        response1 = (200, {\"certificates\": [1, 2, 3, 4]})\n        response2 = (200, {\"certificates\": [5, 6, 7, 8]})\n        mock_req.side_effect = [response1, response2]\n        with self.assertRaises(Exception) as err:\n            self.assertFalse(self.cahandler.certificates_get())\n        self.assertEqual(\n            \"Certificates lookup failed: did not get any total value\",\n            str(err.exception),\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.CAhandler._api_get\")\n    def test_093_certificates_get(self, mock_req):\n        \"\"\"test certificates_get()\"\"\"\n        response1 = (200, {\"certificates\": [1, 2, 3, 4], \"summary\": {\"total\": 9}})\n        response2 = (200, {\"certificates\": [5, 6, 7, 8]})\n        response3 = (200, {\"certificates\": [5, 6, 7, 8]})\n        mock_req.side_effect = [response1, response2, response3]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                [1, 2, 3, 4, 5, 6, 7, 8], self.cahandler.certificates_get()\n            )\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 0, limit: 200, total: 1, buffered: 0\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 200, limit: 200, total: 9, buffered: 4\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"INFO:test_a2c:fetching certs offset: 400, limit: 200, total: 9, buffered: 8\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"INFO:test_a2c:Could not get get new certificate data in loop. Stopping the loop.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.entrust_ca_handler.handler_config_check\")\n    def test_094_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_error.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nfrom unittest.mock import patch, MagicMock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.error import Error\n\n        self.error = Error(False, self.logger)\n\n    def test_001_error__acme_errormessage(self):\n        \"\"\"test badnonce error message\"\"\"\n        self.assertEqual(\n            \"JWS has invalid anti-replay nonce\",\n            self.error._acme_errormessage(\"urn:ietf:params:acme:error:badNonce\"),\n        )\n\n    def test_002_error__acme_errormessage(self):\n        \"\"\"test badnonce error message\"\"\"\n        self.assertEqual(\n            \"The provided contact URI was invalid\",\n            self.error._acme_errormessage(\"urn:ietf:params:acme:error:invalidContact\"),\n        )\n\n    def test_003_error__acme_errormessage(self):\n        \"\"\"test badnonce error message\"\"\"\n        self.assertFalse(\n            self.error._acme_errormessage(\n                \"urn:ietf:params:acme:error:userActionRequired\"\n            )\n        )\n\n    def test_004_error__acme_errormessage(self):\n        \"\"\"test badnonce error message\"\"\"\n        self.assertFalse(\n            self.error._acme_errormessage(\"urn:ietf:params:acme:error:malformed\")\n        )\n\n    def test_005_error__acme_errormessage(self):\n        \"\"\"Error.acme_errormessage for existing value with content\"\"\"\n        self.assertEqual(\n            \"JWS has invalid anti-replay nonce\",\n            self.error._acme_errormessage(\"urn:ietf:params:acme:error:badNonce\"),\n        )\n\n    def test_006_error__acme_errormessage(self):\n        \"\"\"Error.acme_errormessage for existing value without content\"\"\"\n        self.assertFalse(\n            self.error._acme_errormessage(\"urn:ietf:params:acme:error:unauthorized\")\n        )\n\n    def test_007_error__acme_errormessage(self):\n        \"\"\"Error.acme_errormessage for message None\"\"\"\n        self.assertFalse(self.error._acme_errormessage(None))\n\n    def test_008_error__acme_errormessage(self):\n        \"\"\"Error.acme_errormessage for not unknown message\"\"\"\n        self.assertFalse(self.error._acme_errormessage(\"unknown\"))\n\n    def test_009_error_enrich_error(self):\n        \"\"\"Error.enrich_error for valid message and detail\"\"\"\n        self.assertEqual(\n            \"JWS has invalid anti-replay nonce: detail\",\n            self.error.enrich_error(\"urn:ietf:params:acme:error:badNonce\", \"detail\"),\n        )\n\n    def test_010_error_enrich_error(self):\n        \"\"\"Error.enrich_error for valid message, detail and None in error_hash hash\"\"\"\n        self.assertEqual(\n            \"detail\",\n            self.error.enrich_error(\"urn:ietf:params:acme:error:badCSR\", \"detail\"),\n        )\n\n    def test_011_error_enrich_error(self):\n        \"\"\"Error.enrich_error for valid message, no detail and someting in error_hash hash\"\"\"\n        self.assertEqual(\n            \"JWS has invalid anti-replay nonce: None\",\n            self.error.enrich_error(\"urn:ietf:params:acme:error:badNonce\", None),\n        )\n\n    def test_012_error_enrich_error(self):\n        \"\"\"Error.enrich_error for valid message, no detail and nothing in error_hash hash\"\"\"\n        self.assertFalse(\n            self.error.enrich_error(\"urn:ietf:params:acme:error:badCSR\", None)\n        )\n\n    @patch(\"acme_srv.error.Error._acme_errormessage\")\n    def test_013_error_enrich_error(self, mock_error):\n        \"\"\"Error.enrich_error for valid message, no detail and nothing in error_hash hash\"\"\"\n        mock_error.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.error.enrich_error(None, \"\"))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_est_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, Mock\nimport requests\nimport base64\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    def mount():\n        return True\n\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.est_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_proxy_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_parameters_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_password_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_userauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_clientauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_host_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.load_config\")\n    def test_002_config_load(\n        self,\n        mock_load_cfg,\n        mock_host,\n        mock_cla,\n        mock_usa,\n        mock_pass,\n        mock_para,\n        mock_proxy,\n    ):\n        \"\"\"test _config_load - est host configured\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['CAhandler'] = {'api_host': 'api_host', 'foo': 'bar'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertTrue(mock_load_cfg.called)\n        self.assertFalse(mock_host.called)\n        self.assertFalse(mock_cla.called)\n        self.assertFalse(mock_usa.called)\n        self.assertFalse(mock_pass.called)\n        self.assertFalse(mock_para.called)\n        self.assertTrue(mock_proxy.called)\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_proxy_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_parameters_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_password_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_userauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_clientauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_host_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.load_config\")\n    def test_003_config_load(\n        self,\n        mock_load_cfg,\n        mock_host,\n        mock_cla,\n        mock_usa,\n        mock_pass,\n        mock_para,\n        mock_proxy,\n    ):\n        \"\"\"test _config_load - est host configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host\": \"foo_host\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete: either user or client authentication must be configured.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_load_cfg.called)\n        self.assertTrue(mock_host.called)\n        self.assertTrue(mock_cla.called)\n        self.assertTrue(mock_usa.called)\n        self.assertTrue(mock_pass.called)\n        self.assertTrue(mock_para.called)\n        self.assertTrue(mock_proxy.called)\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_proxy_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_parameters_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_password_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_userauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_clientauth_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_host_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.load_config\")\n    def test_004_config_load(\n        self,\n        mock_load_cfg,\n        mock_host,\n        mock_cla,\n        mock_usa,\n        mock_pass,\n        mock_para,\n        mock_proxy,\n    ):\n        \"\"\"test _config_load - est host configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host\": \"foo_host\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        self.cahandler.est_user = \"est_user\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration error: user and client authentication cannot be configured together.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_load_cfg.called)\n        self.assertTrue(mock_host.called)\n        self.assertTrue(mock_cla.called)\n        self.assertTrue(mock_usa.called)\n        self.assertTrue(mock_pass.called)\n        self.assertTrue(mock_para.called)\n        self.assertTrue(mock_proxy.called)\n\n    def test_005_config_host_load(self):\n        \"\"\"test _config_load - est host configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host\": \"foo_host\"}\n        self.cahandler._config_host_load(parser)\n        self.assertEqual(\"foo_host/.well-known/est\", self.cahandler.est_host)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch.dict(\"os.environ\", {\"est_host_var\": \"foo_host\"})\n    def test_006_config_host_load(self):\n        \"\"\"test _config_load - est host configured via environment variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host_variable\": \"est_host_var\"}\n        self.cahandler._config_host_load(parser)\n        self.assertEqual(\"foo_host/.well-known/est\", self.cahandler.est_host)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch.dict(\"os.environ\", {\"est_host_var\": \"foo_host\"})\n    def test_007_config_host_load(self):\n        \"\"\"test _config_load - est host configured  via not existing environment variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host_variable\": \"does_not_exist\"}\n        self.cahandler._config_host_load(parser)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_host_load(parser)\n        self.assertFalse(self.cahandler.est_host)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load est_host_variable:'does_not_exist'\",\n            lcm.output,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch.dict(\"os.environ\", {\"est_host_var\": \"foo_host\"})\n    def test_008_config_host_load(self):\n        \"\"\"test _config_load - est host configured as variable and in cfg\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"est_host_variable\": \"est_host_var\",\n            \"est_host\": \"foo_host_loc\",\n        }\n        self.cahandler._config_host_load(parser)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_host_load(parser)\n        self.assertEqual(\"foo_host_loc/.well-known/est\", self.cahandler.est_host)\n        self.assertIn(\"INFO:test_a2c:Overwrite est_host\", lcm.output)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    def test_009_config_host_load(self):\n        \"\"\"test _config_load - no est host configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_host_load(parser)\n        self.assertIn(\n            'ERROR:test_a2c:Missing \"est_host\" parameter',\n            lcm.output,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.est_ca_handler.load_config\")\n    def test_010_config_host_load(self, mock_cfg):\n        \"\"\"test _config_load - client auth configured but no ca_bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"est_host\": \"foo_host\",\n            \"est_client_key\": \"est_client_key\",\n            \"est_client_cert\": \"est_client_cert\",\n            \"ca_bundle\": False,\n        }\n        mock_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration error: client authentication requires a ca_bundle.\",\n            lcm.output,\n        )\n        self.assertEqual(\"foo_host/.well-known/est\", self.cahandler.est_host)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertTrue(self.cahandler.est_client_cert)\n        self.assertFalse(self.cahandler.ca_bundle)\n\n    def test_011_config_clientauth_load(self):\n        \"\"\"test _config_load - client certificate configured but no key\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_host\": \"foo\", \"est_client_cert\": \"est_client_cert\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_clientauth_load(parser)\n        self.assertFalse(self.cahandler.est_client_cert)\n        self.assertIn(\n            'ERROR:test_a2c:Clientauth configuration incomplete: either \"est_client_key or \"cert_passphrase\" parameter is missing in config file',\n            lcm.output,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.est_client_cert)\n\n    def test_012_config_clientauth_load(self):\n        \"\"\"test _config_load - client certificate configured but no key\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"est_client_key\": \"est_client_key\",\n            \"est_client_cert\": \"est_client_cert\",\n        }\n        self.cahandler.session = Mock()\n        self.cahandler._config_clientauth_load(parser)\n        self.assertTrue(self.cahandler.est_client_cert)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertEqual(\n            (\"est_client_cert\", \"est_client_key\"), self.cahandler.session.cert\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cert_passphrase_load\")\n    @patch(\"examples.ca_handler.est_ca_handler.Pkcs12Adapter\")\n    def test_013_config_clientauth_load(self, mock_pkcs12, mock_load):\n        \"\"\"test _config_load - client certificate configured but no key\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_passphrase\": \"cert_passphrase\",\n            \"est_client_cert\": \"est_client_cert\",\n        }\n        self.cahandler.session = Mock()\n        self.cahandler._config_clientauth_load(parser)\n        self.assertTrue(self.cahandler.est_client_cert)\n        self.assertTrue(mock_pkcs12.called)\n        self.assertTrue(mock_load.called)\n\n    def test_014_cert_passphrase_load(self):\n        \"\"\"_cert_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase\": \"cert_passphrase\"}\n        self.cahandler._cert_passphrase_load(parser)\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_variable\": \"cert_passphrase_variable\"})\n    def test_015_cert_passphrase_load(self):\n        \"\"\"_cert_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_variable\"}\n        self.cahandler._cert_passphrase_load(parser)\n        self.assertEqual(\"cert_passphrase_variable\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_variable\": \"cert_passphrase_variable\"})\n    def test_016_cert_passphrase_load(self):\n        \"\"\"_cert_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_passphrase_variable\": \"cert_passphrase_variable\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._cert_passphrase_load(parser)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite cert_passphrase\",\n            lcm.output,\n        )\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"bar\"})\n    def test_017_cert_passphrase_load(self):\n        \"\"\"_cert_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_variable\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._cert_passphrase_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cert_passphrase_variable:'cert_passphrase_variable'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.cert_passphrase)\n\n    def test_018_config_userauth_load(self):\n        \"\"\"test _config_userauth_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_user\": \"est_user\"}\n        self.cahandler._config_userauth_load(parser)\n        self.assertEqual(\"est_user\", self.cahandler.est_user)\n\n    @patch.dict(\"os.environ\", {\"est_user_var\": \"estuser\"})\n    def test_019_config_userauth_load(self):\n        \"\"\"test _config_userauth_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_user_variable\": \"est_user_var\"}\n        self.cahandler._config_userauth_load(parser)\n        self.assertEqual(\"estuser\", self.cahandler.est_user)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo\"})\n    def test_020_config_userauth_load(self):\n        \"\"\"test _config_userauth_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_user_variable\": \"est_user_var\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_userauth_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load est_user_variable:'est_user_var'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.est_user)\n\n    @patch.dict(\"os.environ\", {\"est_user_var\": \"est_user_var\"})\n    def test_021_config_userauth_load(self):\n        \"\"\"test _config_userauth_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"est_user_variable\": \"est_user_var\",\n            \"est_user\": \"est_user\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_userauth_load(parser)\n        self.assertIn(\n            \"INFO:test_a2c:CAhandler._config_load() overwrite est_user\", lcm.output\n        )\n        self.assertEqual(\"est_user\", self.cahandler.est_user)\n\n    def test_022_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_password\": \"est_password\"}\n        self.cahandler._config_password_load(parser)\n        self.assertEqual(\"est_password\", self.cahandler.est_password)\n\n    @patch.dict(\"os.environ\", {\"est_password_var\": \"est_password_var\"})\n    def test_023_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_password_variable\": \"est_password_var\"}\n        self.cahandler._config_password_load(parser)\n        self.assertEqual(\"est_password_var\", self.cahandler.est_password)\n\n    @patch.dict(\"os.environ\", {\"var\": \"est_password_var\"})\n    def test_024_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"est_password_variable\": \"est_password_var\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_password_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load est_password:'est_password_var'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.est_password)\n\n    @patch.dict(\"os.environ\", {\"est_password_var\": \"est_password_var\"})\n    def test_025_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"est_password_variable\": \"est_password_var\",\n            \"est_password\": \"est_password\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_password_load(parser)\n        self.assertIn(\"INFO:test_a2c:Overwrite est_password\", lcm.output)\n        self.assertEqual(\"est_password\", self.cahandler.est_password)\n\n    def test_026_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler.est_user = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_password_load(parser)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: either \"est_user\" or \"est_password\" parameter is missing in config file',\n            lcm.output,\n        )\n\n    def test_027_config_password_load(self):\n        \"\"\"test _config_password_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler.est_password = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_password_load(parser)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: either \"est_user\" or \"est_password\" parameter is missing in config file',\n            lcm.output,\n        )\n\n    def test_028_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": True}\n        self.cahandler._config_parameters_load(parser)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    def test_029_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": False}\n        self.cahandler._config_parameters_load(parser)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    def test_030_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        self.cahandler._config_parameters_load(parser)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    def test_031_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 10}\n        self.cahandler._config_parameters_load(parser)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(10, self.cahandler.request_timeout)\n\n    def test_032_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"10\"}\n        self.cahandler._config_parameters_load(parser)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(10, self.cahandler.request_timeout)\n\n    def test_033_config_parameters_load(self):\n        \"\"\"test _config_load - ca bundle\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_parameters_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load request_timeout:aa\",\n            lcm.output,\n        )\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.est_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    def test_034_config_proxy_load(self, mock_json, mock_url):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_url.return_value = {\"foo\": \"bar\"}\n        mock_json.return_value = \"foo\"\n        self.cahandler._config_proxy_load(parser)\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.est_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.est_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    def test_035_config_proxy_load(self, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_url.return_value = {\"host\": \"bar:8888\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        self.cahandler._config_proxy_load(parser)\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_chk.called)\n        self.assertEqual(\n            {\"http\": \"proxy.bar.local\", \"https\": \"proxy.bar.local\"},\n            self.cahandler.proxy,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.est_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.est_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    def test_036_config_proxy_load(self, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_url.return_value = {\"host\": \"bar\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_proxy_load(parser)\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_chk.called)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load proxy_server_list from configuration: not enough values to unpack (expected 2, got 1)\",\n            lcm.output,\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    def test_037_revoke(self):\n        \"\"\"test revocation\"\"\"\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Revocation is not supported.\",\n            ),\n            self.cahandler.revoke(\"cert\", \"rev_reason\", \"rev_date\"),\n        )\n\n    def test_038_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_039_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch.object(requests, \"get\")\n    def test_040__cacerts_get(self, mock_req, mock_to_pem, _mock_b64):\n        \"\"\"test _cacerts_get() successful run by using client certs\"\"\"\n        self.cahandler.session = Mock()\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.text = \"mock return\"\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        self.assertEqual((None, \"pem\"), self.cahandler._cacerts_get())\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch.object(requests, \"get\")\n    def test_041__cacerts_get(self, mock_req, mock_to_pem, _mock_b64):\n        \"\"\"test _cacerts_get() successful run by using client certs\"\"\"\n        self.cahandler.session = Mock()\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.text = \"mock return\"\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_user = \"est_user\"\n        self.cahandler.est_password = \"est_password\"\n        self.assertEqual((None, \"pem\"), self.cahandler._cacerts_get())\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch.object(requests, \"get\")\n    def test_042__cacerts_get(self, mock_req, mock_to_pem, _mock_b64):\n        \"\"\"test _cacerts_get() no est_host parameter\"\"\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.text = \"mock return\"\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((None, None), self.cahandler._cacerts_get())\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: \"est_host\" parameter is missing',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    def test_043__cacerts_get(self, mock_to_pem, _mock_b64):\n        \"\"\"test _cacerts_get() request.get triggers exception\"\"\"\n        self.cahandler.session = Mock()\n        mock_to_pem.side_effect = Exception(\"exc_cacerts_get\")\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._cacerts_get()\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting the CA certificates: exc_cacerts_get\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    def test_044__simpleenroll(self, mock_to_pem, _mock_b64):\n        \"\"\"test _cacerts_get() successful run\"\"\"\n        mockresponse = Mock()\n        self.cahandler.session = Mock()\n        mockresponse.text = \"mock return\"\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        self.assertEqual((None, \"pem\"), self.cahandler._simpleenroll(\"csr\"))\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    def test_045__simpleenroll(self, mock_to_pem, mock_b64):\n        \"\"\"test _cacerts_get() successful run\"\"\"\n        self.cahandler.session = Mock()\n        mock_b64.side_effect = Exception(\"exc_simple_enroll\")\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_client_cert = \"est_client_cert\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"exc_simple_enroll\", None), self.cahandler._simpleenroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error: exc_simple_enroll\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._pkcs7_to_pem\")\n    def test_046__simpleenroll(self, mock_to_pem, mock_b64):\n        \"\"\"test _cacerts_get() successful run\"\"\"\n        self.cahandler.session = Mock()\n        mock_b64.side_effect = Exception(\"exc_simple_enroll\")\n        mock_to_pem.return_value = \"pem\"\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.ca_bundle = [\"foo_bundle\"]\n        self.cahandler.est_user = \"est_user\"\n        self.cahandler.est_password = \"est_password\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"exc_simple_enroll\", None), self.cahandler._simpleenroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment error: exc_simple_enroll\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_047_enroll(self, mock_ca):\n        \"\"\"test certificate enrollment _cacert_get returns error\"\"\"\n        mock_ca.return_value = (\"Error\", None)\n        self.cahandler.est_host = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"Error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while fetching the CA certificates: Error\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_048_enroll(self, mock_ca):\n        \"\"\"test certificate enrollment no error but now ca_certs\"\"\"\n        mock_ca.return_value = (None, None)\n        self.cahandler.est_host = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"no CA certificates found\", None, None, None),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\"ERROR:test_a2c:No CA certificates found\", lcm.output)\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_049_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment _simpleenroll returns error\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (\"Error\", None)\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"Error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\"ERROR:test_a2c:Simpleenroll error: Error\", lcm.output)\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_050_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment _simpleenroll returns certificate\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (None, \"cert\")\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        self.assertEqual(\n            (None, \"certca_pem\", \"cert\", None), self.cahandler.enroll(\"csr\")\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_051_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment replace CERT BEGIN\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (None, \"-----BEGIN CERTIFICATE-----\\ncert\")\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        self.assertEqual(\n            (None, \"-----BEGIN CERTIFICATE-----\\ncertca_pem\", \"cert\", None),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_052_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment replace CERT END\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (None, \"cert-----END CERTIFICATE-----\\n\")\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        self.assertEqual(\n            (None, \"cert-----END CERTIFICATE-----\\nca_pem\", \"cert\", None),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_053_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment replace CERT BEGIN AND END\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (\n            None,\n            \"-----BEGIN CERTIFICATE-----\\ncert-----END CERTIFICATE-----\\n\",\n        )\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\ncert-----END CERTIFICATE-----\\nca_pem\",\n                \"cert\",\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._simpleenroll\")\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._cacerts_get\")\n    def test_054_enroll(self, mock_ca, mock_enroll):\n        \"\"\"test certificate enrollment replace CERT BEGIN AND END and \\n\"\"\"\n        mock_ca.return_value = (None, \"ca_pem\")\n        mock_enroll.return_value = (\n            None,\n            \"-----BEGIN CERTIFICATE-----\\ncert\\n-----END CERTIFICATE-----\\n\\n\",\n        )\n        self.cahandler.est_host = \"foo\"\n        self.cahandler.est_user = \"est_usr\"\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\ncert\\n-----END CERTIFICATE-----\\n\\nca_pem\",\n                \"cert\",\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_load\")\n    def test_055__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.est_ca_handler.CAhandler._config_load\")\n    def test_056__enter__(self, mock_cfg):\n        \"\"\"test enter api hosts defined\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.est_host = \"api_host\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    def test_057__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem default output\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        with open(self.dir_path + \"/ca/certs.pem\", \"r\") as fso:\n            result = fso.read()\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content))\n\n    def test_058__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output string\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        with open(self.dir_path + \"/ca/certs.pem\", \"r\") as fso:\n            result = fso.read()\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"string\"))\n\n    def test_059__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        result = [\n            \"-----BEGIN CERTIFICATE-----\\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\\njX1vlY35Ofonc4+6dRVamBiF9A==\\n-----END CERTIFICATE-----\\n\",\n            \"-----BEGIN CERTIFICATE-----\\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\\nfvkG2Q==\\n-----END CERTIFICATE-----\\n\",\n        ]\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"list\"))\n\n    def test_060__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        result = None\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"unknown\"))\n\n    def test_061__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n\n        file_content = base64.b64decode(\n            \"MIIK9AYJKoZIhvcNAQcCoIIK5TCCCuECAQExADALBgkqhkiG9w0BBwGgggrHMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1NDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEPMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8jqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/qkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT//WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCVXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9hcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLBZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB15Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilMGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8hH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dmKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYDVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77WvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP96YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqHJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwCzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ32tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/M7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5Z3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsFzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4tjX1vlY35Ofonc4+6dRVamBiF9DCCBXAwggNYoAMCAQICCHry008TjKGYMA0GCSqGSIb3DQEBCwUAMCsxFzAVBgNVBAsTDmFjbWUyY2VydGlmaWVyMRAwDgYDVQQDEwdyb290LWNhMB4XDTIwMDUyNzAwMDAwMFoXDTMwMDUyNjIzNTk1OVowKzEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCcuFGR3WYGLeuJP6xWpqAuu+rWL7Wm3roqlcNXOqFWSPPe3BSxugWMMq9hGo+7Ra6kyQ3jDeL2UrnS7Jiw6upvCsF/64j81EyJXIzOWiDADWa/ayxLNzVrXIr6JQeWkNbJYXVYrVDy7sbBZ2HkE8sRcj+5Z4PTP2eNNyixvKYXZiozNLyZGo+Drijl391LFqlkGMkZf5rNO8VY9NrqtPC5KHjvo7UIrL8lV1EXWgnHmbciv2QUOzRQrGytddnFUdXtmiaJezSQAOlpuogcwHZAANpd5IeNEi6BG2omlTIsSzdr4pSGTjgKA11+Pk+oq/ipw1UidsruXPziTMLl8B64ey4INb7BUeUzXoZJ4Y1LljlDvtE5Cj4NgOyk4O9jmdpjnC2SG8c69T+UUb3Zi0Cz60xdhCb6UDzZm16jd2VVhL3x045JExWP3bDk7xU4Eq4tec2CnIfL6LXFO8/gUIYJjLcDtiYTzJmegAXfbJCO4o1qcDpbQIcbXaATuk+ggQqxNsl3Olfz8sgCnBYJTZiIIbeaF7JxPrm/3bcfH/SHmv8TT3aOWhsvH3WoraJQytepHdym+zhOBzByMDscRdQRAKnq8cYWyzEQa/IUUmSOXLy81i76QEOc7oYw+ld2/QWBXeLowLt85d5m83W2IxaBjl+mgWWhg8ZXODtlux8Z7QIDAQABo4GXMIGUMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAxlb7kxWGbRAJM92n/cESgw7wLMMB8GA1UdIwQYMBaAFAxlb7kxWGbRAJM92n/cESgw7wLMMA4GA1UdDwEB/wQEAwIBhjARBglghkgBhvhCAQEEBAMCAAcwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAgEAI5KO3V/ogoE/ptyMtVYOo+zEXrzwM6tZah0UTTXbdnLed9KSLrexb+VdsWJN+ZGvovxvl8jr012vbwFCogbYkZ1D7F7uFHEuwnKwlxMx8eHjrR56ecA4TtBcefzlGU2j/i+z3dRg/4/ed4m8eWzGvzPUY/kuPOp7Lee7bg0ZhAGrxQ4jHei94x+GEnJI5iB9rkngyGkWvZOmoNO+15lob1WEPbbke0Rm0rrldxXOqanBW21qaC4TUXBXPoW1CjrCpdnD/kBCj/x9rN1jJZaoACimXjvtSjrBqNQg7OO+WyF8ggaRWx9hzj2rZrt8mUUPX4/bWvRFuOp99wxUTS4pxVmuFibOMi1T9Y7oHtkwjsgNbetYsvvkUV0ht3uzRWKxGbgobdRHQwHcyFGPPZvTQj8KA2EuAmoYJ35JDu2EHun+sqiuorMn/GRXKdbhefHhEQ4hfxQ6kvJy5gnVEbiOegw6Dbw6YoOSf/UTVhvLxL7dqe0K43mdxAGrceSbmLcvzSkx2cCVpOdQoMxIMw8MxNiSIqnpXbS+XdEpenlbr9BtOARMrl8RFqYFVcwUuKhBSAUp0yc4LtT15iHd8i74Nja/DSr8MmZjecShadAPQVqNec1tT1w2g/pd/aE2A+2oI15M0wI2CHHzSewEyGBQ9MQLzQDn+9LO2jbqRX75BtmhADEA\"\n        )\n        result = [\n            \"-----BEGIN CERTIFICATE-----\\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\\njX1vlY35Ofonc4+6dRVamBiF9A==\\n-----END CERTIFICATE-----\\n\",\n            \"-----BEGIN CERTIFICATE-----\\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\\nfvkG2Q==\\n-----END CERTIFICATE-----\\n\",\n        ]\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"list\"))\n\n    @patch(\"examples.ca_handler.est_ca_handler.handler_config_check\")\n    def test_062_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_helper.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport configparser\nimport sys\nimport datetime\nimport socket\nfrom unittest.mock import patch, MagicMock, Mock\nimport dns.resolver\nimport base64\nimport requests\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    profiles = {}\n    header_info_field = \"header_info_field\"\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        from acme_srv.helper import (\n            b64decode_pad,\n            b64_decode,\n            b64_encode,\n            b64_url_encode,\n            b64_url_recode,\n            b64_url_decode,\n            convert_string_to_byte,\n            convert_byte_to_string,\n            decode_message,\n            decode_deserialize,\n            get_url,\n            generate_random_string,\n            signature_check,\n            validate_email,\n            uts_to_date_utc,\n            date_to_uts_utc,\n            load_config,\n            cert_serial_get,\n            cert_san_get,\n            cert_san_pyopenssl_get,\n            cert_dates_get,\n            build_pem_file,\n            date_to_datestr,\n            datestr_to_date,\n            dkeys_lower,\n            csr_cn_get,\n            cert_pubkey_get,\n            csr_pubkey_get,\n            url_get,\n            url_get_with_own_dns,\n            dns_server_list_load,\n            csr_san_get,\n            csr_san_byte_get,\n            csr_extensions_get,\n            fqdn_resolve,\n            fqdn_in_san_check,\n            sha256_hash,\n            sha256_hash_hex,\n            cert_der2pem,\n            cert_pem2der,\n            cert_extensions_get,\n            csr_dn_get,\n            logger_setup,\n            logger_info,\n            print_debug,\n            jwk_thumbprint_get,\n            allowed_gai_family,\n            patched_create_connection,\n            validate_csr,\n            servercert_get,\n            txt_get,\n            proxystring_convert,\n            proxy_check,\n            handle_exception,\n            ca_handler_load,\n            eab_handler_load,\n            hooks_load,\n            error_dic_get,\n            _logger_nonce_modify,\n            _logger_certificate_modify,\n            _logger_token_modify,\n            _logger_challenges_modify,\n            config_check,\n            cert_issuer_get,\n            cert_cn_get,\n            string_sanitize,\n            pembundle_to_list,\n            certid_asn1_get,\n            certid_check,\n            certid_hex_get,\n            v6_adjust,\n            ipv6_chk,\n            ip_validate,\n            header_info_get,\n            encode_url,\n            uts_now,\n            cert_ski_get,\n            cert_ski_pyopenssl_get,\n            cert_aki_get,\n            cert_aki_pyopenssl_get,\n            validate_fqdn,\n            validate_ip,\n            validate_identifier,\n            client_parameter_validate,\n            header_info_lookup,\n            config_eab_profile_load,\n            config_headerinfo_load,\n            config_profile_load,\n            config_proxy_load,\n            allowed_domainlist_check,\n            eab_profile_string_check,\n            eab_profile_list_check,\n            eab_profile_check,\n            eab_profile_header_info_check,\n            cert_extensions_py_openssl_get,\n            cryptography_version_get,\n            cn_validate,\n            csr_subject_get,\n            eab_profile_subject_string_check,\n            eab_profile_subject_check,\n            csr_cn_lookup,\n            request_operation,\n            enrollment_config_log,\n            config_enroll_config_log_load,\n            config_allowed_domainlist_load,\n            config_async_mode_load,\n            is_domain_whitelisted,\n            allowed_domainlist_check,\n            radomize_parameter_list,\n            profile_lookup,\n            eab_profile_revocation_check,\n            handler_config_check,\n        )\n\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.allowed_gai_family = allowed_gai_family\n        self.b64_decode = b64_decode\n        self.b64_encode = b64_encode\n        self.b64_url_encode = b64_url_encode\n        self.b64_url_recode = b64_url_recode\n        self.b64decode_pad = b64decode_pad\n        self.build_pem_file = build_pem_file\n        self.ca_handler_load = ca_handler_load\n        self.cert_dates_get = cert_dates_get\n        self.cert_extensions_get = cert_extensions_get\n        self.cert_extensions_py_openssl_get = cert_extensions_py_openssl_get\n        self.certid_asn1_get = certid_asn1_get\n        self.certid_check = certid_check\n        self.cert_pubkey_get = cert_pubkey_get\n        self.cert_san_get = cert_san_get\n        self.cert_san_pyopenssl_get = cert_san_pyopenssl_get\n        self.cert_serial_get = cert_serial_get\n        self.cert_aki_get = cert_aki_get\n        self.cert_aki_pyopenssl_get = cert_aki_pyopenssl_get\n        self.cert_ski_get = cert_ski_get\n        self.cert_ski_pyopenssl_get = cert_ski_pyopenssl_get\n        self.cert_issuer_get = cert_issuer_get\n        self.cert_pem2der = cert_pem2der\n        self.cert_der2pem = cert_der2pem\n        self.cert_cn_get = cert_cn_get\n        self.certid_hex_get = certid_hex_get\n        self.config_check = config_check\n        self.convert_byte_to_string = convert_byte_to_string\n        self.convert_string_to_byte = convert_string_to_byte\n        self.cryptography_version_get = cryptography_version_get\n        self.csr_cn_get = csr_cn_get\n        self.csr_dn_get = csr_dn_get\n        self.csr_extensions_get = csr_extensions_get\n        self.csr_pubkey_get = csr_pubkey_get\n        self.csr_san_get = csr_san_get\n        self.date_to_datestr = date_to_datestr\n        self.date_to_uts_utc = date_to_uts_utc\n        self.datestr_to_date = datestr_to_date\n        self.decode_deserialize = decode_deserialize\n        self.decode_message = decode_message\n        self.dkeys_lower = dkeys_lower\n        self.dns_server_list_load = dns_server_list_load\n        self.eab_handler_load = eab_handler_load\n        self.error_dic_get = error_dic_get\n        self.fqdn_resolve = fqdn_resolve\n        self.fqdn_in_san_check = fqdn_in_san_check\n        self.generate_random_string = generate_random_string\n        self.get_url = get_url\n        self.client_parameter_validate = client_parameter_validate\n        self.header_info_lookup = header_info_lookup\n        self.hooks_load = hooks_load\n        self.ipv6_chk = ipv6_chk\n        self.jwk_thumbprint_get = jwk_thumbprint_get\n        self.load_config = load_config\n        self.logger_setup = logger_setup\n        self.logger_info = logger_info\n        self.logger_nonce_modify = _logger_nonce_modify\n        self.logger_certificate_modify = _logger_certificate_modify\n        self.logger_token_modify = _logger_token_modify\n        self.logger_challenges_modify = _logger_challenges_modify\n        self.patched_create_connection = patched_create_connection\n        self.pembundle_to_list = pembundle_to_list\n        self.print_debug = print_debug\n        self.proxy_check = proxy_check\n        self.servercert_get = servercert_get\n        self.signature_check = signature_check\n        self.txt_get = txt_get\n        self.url_get = url_get\n        self.url_get_with_own_dns = url_get_with_own_dns\n        self.uts_to_date_utc = uts_to_date_utc\n        self.validate_email = validate_email\n        self.validate_ip = validate_ip\n        self.validate_fqdn = validate_fqdn\n        self.validate_identifier = validate_identifier\n        self.validate_csr = validate_csr\n        self.sha256_hash = sha256_hash\n        self.sha256_hash_hex = sha256_hash_hex\n        self.string_sanitize = string_sanitize\n        self.proxystring_convert = proxystring_convert\n        self.v6_adjust = v6_adjust\n        self.handle_exception = handle_exception\n        self.header_info_get = header_info_get\n        self.csr_san_byte_get = csr_san_byte_get\n        self.encode_url = encode_url\n        self.uts_now = uts_now\n        self.ip_validate = ip_validate\n        self.config_headerinfo_load = config_headerinfo_load\n        self.config_eab_profile_load = config_eab_profile_load\n        self.config_allowed_domainlist_load = config_allowed_domainlist_load\n        self.allowed_domainlist_check = allowed_domainlist_check\n        self.eab_profile_string_check = eab_profile_string_check\n        self.eab_profile_list_check = eab_profile_list_check\n        self.eab_profile_check = eab_profile_check\n        self.eab_profile_header_info_check = eab_profile_header_info_check\n        self.cn_validate = cn_validate\n        self.csr_subject_get = csr_subject_get\n        self.eab_profile_subject_string_check = eab_profile_subject_string_check\n        self.eab_profile_subject_check = eab_profile_subject_check\n        self.csr_cn_lookup = csr_cn_lookup\n        self.request_operation = request_operation\n        self.enrollment_config_log = enrollment_config_log\n        self.config_enroll_config_log_load = config_enroll_config_log_load\n        self.config_async_mode_load = config_async_mode_load\n        self.is_domain_whitelisted = is_domain_whitelisted\n        self.allowed_domainlist_check = allowed_domainlist_check\n        self.radomize_parameter_list = radomize_parameter_list\n        self.config_profile_load = config_profile_load\n        self.config_proxy_load = config_proxy_load\n        self.profile_lookup = profile_lookup\n        self.b64_url_decode = b64_url_decode\n        self.eab_profile_revocation_check = eab_profile_revocation_check\n        self.handler_config_check = handler_config_check\n\n    def test_001_helper_b64decode_pad(self):\n        \"\"\"test b64decode_pad() method with a regular base64 encoded string\"\"\"\n        self.assertEqual(\n            \"this-is-foo-correctly-padded\",\n            self.b64decode_pad(self.logger, \"dGhpcy1pcy1mb28tY29ycmVjdGx5LXBhZGRlZA==\"),\n        )\n\n    def test_002_helper_b64decode_pad(self):\n        \"\"\"test b64decode_pad() method with a regular base64 encoded string\"\"\"\n        self.assertEqual(\n            \"this-is-foo-with-incorrect-padding\",\n            self.b64decode_pad(\n                self.logger, \"dGhpcy1pcy1mb28td2l0aC1pbmNvcnJlY3QtcGFkZGluZw\"\n            ),\n        )\n\n    def test_003_helper_b64decode_pad(self):\n        \"\"\"test b64 decoding failure\"\"\"\n        self.assertEqual(\n            \"ERR: b64 decoding error\", self.b64decode_pad(self.logger, \"b\")\n        )\n\n    def test_004_helper_decode_deserialize(self):\n        \"\"\"test successful deserialization of a b64 encoded string\"\"\"\n        self.assertEqual(\n            {\"a\": \"b\", \"c\": \"d\"},\n            self.decode_deserialize(self.logger, \"eyJhIiA6ICJiIiwgImMiIDogImQifQ==\"),\n        )\n\n    def test_005_helper_decode_deserialize(self):\n        \"\"\"test failed deserialization of a b64 encoded string\"\"\"\n        self.assertEqual(\n            \"ERR: Json decoding error\",\n            self.decode_deserialize(\n                self.logger, \"Zm9vLXdoaWNoLWNhbm5vdC1iZS1qc29uaXplZA==\"\n            ),\n        )\n\n    def test_006_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertTrue(self.validate_email(self.logger, \"foo@example.com\"))\n\n    def test_007_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertTrue(self.validate_email(self.logger, \"mailto:foo@example.com\"))\n\n    def test_008_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertTrue(self.validate_email(self.logger, \"mailto: foo@example.com\"))\n\n    def test_009_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertTrue(\n            self.validate_email(\n                self.logger, [\"mailto: foo@example.com\", \"mailto: bar@example.com\"]\n            )\n        )\n\n    def test_010_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertFalse(self.validate_email(self.logger, \"example.com\"))\n\n    def test_011_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertFalse(self.validate_email(self.logger, \"me@exam,ple.com\"))\n\n    def test_012_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertFalse(\n            self.validate_email(\n                self.logger, [\"mailto: foo@exa,mple.com\", \"mailto: bar@example.com\"]\n            )\n        )\n\n    def test_013_helper_validate_email(self):\n        \"\"\"validate normal email\"\"\"\n        self.assertFalse(\n            self.validate_email(\n                self.logger, [\"mailto: foo@example.com\", \"mailto: bar@exa,mple.com\"]\n            )\n        )\n\n    def test_014_helper_signature_check(self):\n        \"\"\"successful validation of singature\"\"\"\n        mkey = {\n            \"alg\": \"RS256\",\n            \"e\": \"AQAB\",\n            \"kty\": \"RSA\",\n            \"n\": \"2CFMV4MK6Uo_2GQWa0KVWlzffgSDiLwur4ujSZkCRzbA3w5p1ABJgr7l_P84HpRv8R8rGL67hqmDJuT52mGD6fMVAhHPX5pSdtyZlQQuzpXonzNmHbG1DbMSiXrxg5jWVXchCxHx82wAt9Kf13O5ATxD0WOBB5FffpqQHh8zTf29jTL4vBd8N57ce17ZgNWl_EcoByjigqNFJcO0rrvrf6xyNaO9nbun4PAMJTLbfVa6CiEqjnjYMX80VYLH4fCqsAZgxIoli_D2j9P5Kq6KZZUL_bZ2QQV4UuwWZvh6tcA393YQLeMARnhWI6dqlZVdcU74NXi9NhSxcMkM8nZZ8Q\",\n        }\n        message = '{\"protected\": \"eyJub25jZSI6ICI3N2M3MmViMDE5NDc0YzBjOWIzODk5MmU4ZjRkMDIzYSIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIn0\",\"payload\": \"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9\",\"signature\": \"QYbMYZ1Dk8dHKqOwWBQHvWdnGD7donGZObb2Ry_Y5PsHpcTrj8Y2CM57SNVAR9V0ePg4vhK3-IbwYAKbhZV8jF7E-ylZaYm4PSQcumKLI55qvDiEvDiZ0gmjf_GAcsC40TwBa11lzR1u0dQYxOlQ_y9ak6705c5bM_V4_ttQeslJXCfVIQoV-sZS0Z6tJfy5dPVDR7JYG77bZbD3K-HCCaVbT7ilqcf00rA16lvw13zZnIgbcZsbW-eJ2BM_QxE24PGqc_vMfAxIiUG0VY7DqrKumLs91lHHTEie8I-CapH6AetsBhGtRcB6EL_Rn6qGQZK9YBpvoXANv_qF2-zQkQ\"}'\n        self.assertEqual((True, None), self.signature_check(self.logger, message, mkey))\n\n    def test_015_helper_signature_check(self):\n        \"\"\"failed validatio of singature  wrong key\"\"\"\n        mkey = {\n            \"alg\": \"rs256\",\n            \"e\": \"AQAB\",\n            \"kty\": \"RSA\",\n            \"n\": \"zncgRHCp22-29g9FO4Hn02iyS1Fo4Y1tB-6cucH1yKSxM6bowjAaVa4HkAnIxgF6Zj9qLROgQR84YjMPeNkq8woBRz1aziDiTIOc0D2aXvLgZbuFGesvxoSGd6uyxjyyV7ONwZEpB8QtDW0I3shlhosKB3Ni1NFu55bPUP9RvxUdPzRRuhxUMHc1CXre1KR0eQmQdNZT6tgQVxpv2lb-iburBADjivBRyrI3k3NmXkYknBggAu8JInaFY4T8pVK0jTwP-f3-0eAV1bg99Rm7uXNXl7SKpQ3oGihwy2OK-XAc59v6C3n4Wq9QpzGkFWsOOlp4zEf13L3_UKugeExEqw\",\n        }\n        message = '{\"protected\": \"eyJub25jZSI6ICI3N2M3MmViMDE5NDc0YzBjOWIzODk5MmU4ZjRkMDIzYSIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIn0\",\"payload\": \"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9\",\"signature\": \"QYbMYZ1Dk8dHKqOwWBQHvWdnGD7donGZObb2Ry_Y5PsHpcTrj8Y2CM57SNVAR9V0ePg4vhK3-IbwYAKbhZV8jF7E-ylZaYm4PSQcumKLI55qvDiEvDiZ0gmjf_GAcsC40TwBa11lzR1u0dQYxOlQ_y9ak6705c5bM_V4_ttQeslJXCfVIQoV-sZS0Z6tJfy5dPVDR7JYG77bZbD3K-HCCaVbT7ilqcf00rA16lvw13zZnIgbcZsbW-eJ2BM_QxE24PGqc_vMfAxIiUG0VY7DqrKumLs91lHHTEie8I-CapH6AetsBhGtRcB6EL_Rn6qGQZK9YBpvoXANv_qF2-zQkQ\"}'\n\n        if int(\"%i%i\" % (sys.version_info[0], sys.version_info[1])) <= 36:\n            result = (\n                False,\n                \"Verification failed for all signatures[\\\"Failed: [InvalidJWSSignature('Verification failed',)]\\\"]\",\n            )\n        else:\n            result = (\n                False,\n                \"Verification failed for all signatures[\\\"Failed: [InvalidJWSSignature('Verification failed')]\\\"]\",\n            )\n\n        self.assertEqual(result, self.signature_check(self.logger, message, mkey))\n\n    def test_016_helper_signature_check(self):\n        \"\"\"failed validatio of singature  faulty key\"\"\"\n        mkey = {\n            \"alg\": \"rs256\",\n            \"e\": \"AQAB\",\n            \"n\": \"zncgRHCp22-29g9FO4Hn02iyS1Fo4Y1tB-6cucH1yKSxM6bowjAaVa4HkAnIxgF6Zj9qLROgQR84YjMPeNkq8woBRz1aziDiTIOc0D2aXvLgZbuFGesvxoSGd6uyxjyyV7ONwZEpB8QtDW0I3shlhosKB3Ni1NFu55bPUP9RvxUdPzRRuhxUMHc1CXre1KR0eQmQdNZT6tgQVxpv2lb-iburBADjivBRyrI3k3NmXkYknBggAu8JInaFY4T8pVK0jTwP-f3-0eAV1bg99Rm7uXNXl7SKpQ3oGihwy2OK-XAc59v6C3n4Wq9QpzGkFWsOOlp4zEf13L3_UKugeExEqw\",\n        }\n        message = '{\"protected\": \"eyJub25jZSI6ICI3N2M3MmViMDE5NDc0YzBjOWIzODk5MmU4ZjRkMDIzYSIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIn0\",\"payload\": \"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9\",\"signature\": \"QYbMYZ1Dk8dHKqOwWBQHvWdnGD7donGZObb2Ry_Y5PsHpcTrj8Y2CM57SNVAR9V0ePg4vhK3-IbwYAKbhZV8jF7E-ylZaYm4PSQcumKLI55qvDiEvDiZ0gmjf_GAcsC40TwBa11lzR1u0dQYxOlQ_y9ak6705c5bM_V4_ttQeslJXCfVIQoV-sZS0Z6tJfy5dPVDR7JYG77bZbD3K-HCCaVbT7ilqcf00rA16lvw13zZnIgbcZsbW-eJ2BM_QxE24PGqc_vMfAxIiUG0VY7DqrKumLs91lHHTEie8I-CapH6AetsBhGtRcB6EL_Rn6qGQZK9YBpvoXANv_qF2-zQkQ\"}'\n        if sys.version_info[0] < 3:\n            self.assertEqual(\n                (False, \"Unknown type \\\"None\\\", valid types are: ['RSA', 'EC', 'oct']\"),\n                self.signature_check(self.logger, message, mkey),\n            )\n        else:\n            self.assertEqual(\n                (\n                    False,\n                    \"Unknown type \\\"None\\\", valid types are: ['EC', 'RSA', 'oct', 'OKP']\",\n                ),\n                self.signature_check(self.logger, message, mkey),\n            )\n\n    def test_017_helper_signature_check(self):\n        \"\"\"failed validatio of singature  no key\"\"\"\n        mkey = {}\n        message = '{\"protected\": \"eyJub25jZSI6ICI3N2M3MmViMDE5NDc0YzBjOWIzODk5MmU4ZjRkMDIzYSIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYWNjdC8xIn0\",\"payload\": \"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9\",\"signature\": \"QYbMYZ1Dk8dHKqOwWBQHvWdnGD7donGZObb2Ry_Y5PsHpcTrj8Y2CM57SNVAR9V0ePg4vhK3-IbwYAKbhZV8jF7E-ylZaYm4PSQcumKLI55qvDiEvDiZ0gmjf_GAcsC40TwBa11lzR1u0dQYxOlQ_y9ak6705c5bM_V4_ttQeslJXCfVIQoV-sZS0Z6tJfy5dPVDR7JYG77bZbD3K-HCCaVbT7ilqcf00rA16lvw13zZnIgbcZsbW-eJ2BM_QxE24PGqc_vMfAxIiUG0VY7DqrKumLs91lHHTEie8I-CapH6AetsBhGtRcB6EL_Rn6qGQZK9YBpvoXANv_qF2-zQkQ\"}'\n        self.assertEqual(\n            (False, \"No key specified.\"),\n            self.signature_check(self.logger, message, mkey),\n        )\n\n    def test_018_helper_uts_to_date_utc(self):\n        \"\"\"test uts_to_date_utc for a given format\"\"\"\n        self.assertEqual(\"2018-12-01\", self.uts_to_date_utc(1543640400, \"%Y-%m-%d\"))\n\n    def test_019_helper_uts_to_date_utc(self):\n        \"\"\"test uts_to_date_utc without format\"\"\"\n        self.assertEqual(\"2018-12-01T05:00:00Z\", self.uts_to_date_utc(1543640400))\n\n    def test_020_helper_date_to_uts_utc(self):\n        \"\"\"test date_to_uts_utc for a given format\"\"\"\n        self.assertEqual(1543622400, self.date_to_uts_utc(\"2018-12-01\", \"%Y-%m-%d\"))\n\n    def test_021_helper_date_to_uts_utc(self):\n        \"\"\"test date_to_uts_utc without format\"\"\"\n        self.assertEqual(1543640400, self.date_to_uts_utc(\"2018-12-01T05:00:00\"))\n\n    def test_022_helper_date_to_uts_utc(self):\n        \"\"\"test date_to_uts_utc with a datestring\"\"\"\n        timestamp = datetime.datetime(2018, 12, 1, 5, 0, 1)\n        self.assertEqual(1543640401, self.date_to_uts_utc(timestamp))\n\n    def test_023_helper_generate_random_string(self):\n        \"\"\"test date_to_uts_utc without format\"\"\"\n        self.assertEqual(5, len(self.generate_random_string(self.logger, 5)))\n\n    def test_024_helper_generate_random_string(self):\n        \"\"\"test date_to_uts_utc without format\"\"\"\n        self.assertEqual(15, len(self.generate_random_string(self.logger, 15)))\n\n    def test_025_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 1 char\"\"\"\n        self.assertEqual(\"fafafaf=\", self.b64_url_recode(self.logger, \"fafafaf\"))\n\n    def test_026_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 2 char\"\"\"\n        self.assertEqual(\"fafafa==\", self.b64_url_recode(self.logger, \"fafafa\"))\n\n    def test_027_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 3 char\"\"\"\n        self.assertEqual(\"fafaf===\", self.b64_url_recode(self.logger, \"fafaf\"))\n\n    def test_028_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - no padding\"\"\"\n        self.assertEqual(\"fafafafa\", self.b64_url_recode(self.logger, \"fafafafa\"))\n\n    def test_029_helper_b64_url_recode(self):\n        \"\"\"test base64url replace - with + and pad\"\"\"\n        self.assertEqual(\"fafa+f==\", self.b64_url_recode(self.logger, \"fafa-f\"))\n\n    def test_030_helper_b64_url_recode(self):\n        \"\"\"test base64url replace _ with / and pad\"\"\"\n        self.assertEqual(\"fafa/f==\", self.b64_url_recode(self.logger, \"fafa_f\"))\n\n    def test_031_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 1 char\"\"\"\n        self.assertEqual(\"fafafaf=\", self.b64_url_recode(self.logger, b\"fafafaf\"))\n\n    def test_032_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 2 char\"\"\"\n        self.assertEqual(\"fafafa==\", self.b64_url_recode(self.logger, b\"fafafa\"))\n\n    def test_033_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - add padding for 3 char\"\"\"\n        self.assertEqual(\"fafaf===\", self.b64_url_recode(self.logger, b\"fafaf\"))\n\n    def test_034_helper_b64_url_recode(self):\n        \"\"\"test base64url recode to base64 - no padding\"\"\"\n        self.assertEqual(\"fafafafa\", self.b64_url_recode(self.logger, b\"fafafafa\"))\n\n    def test_035_helper_b64_url_recode(self):\n        \"\"\"test base64url replace - with + and pad\"\"\"\n        self.assertEqual(\"fafa+f==\", self.b64_url_recode(self.logger, b\"fafa-f\"))\n\n    def test_036_helper_b64_url_recode(self):\n        \"\"\"test base64url replace _ with / and pad\"\"\"\n        self.assertEqual(\"fafa/f==\", self.b64_url_recode(self.logger, b\"fafa_f\"))\n\n    def test_037_helper_b64_url_recode(self):\n        \"\"\"test base64url replace _ with / and pad\"\"\"\n        self.assertEqual(\"fafa/f==\", self.b64_url_recode(self.logger, b\"fafa_f\"))\n\n    def test_038_helper_decode_message(self):\n        \"\"\"decode message with empty payload - certbot issue\"\"\"\n        data_dic = '{\"protected\": \"eyJub25jZSI6ICIyNmU2YTQ2ZWZhZGQ0NzdkOTA4ZDdjMjAxNGU0OWIzNCIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYXV0aHovUEcxODlGRnpmYW8xIiwgImtpZCI6ICJodHRwOi8vbGFwdG9wLm5jbG0tc2FtYmEubG9jYWwvYWNtZS9hY2N0L3l1WjFHVUpiNzZaayIsICJhbGciOiAiUlMyNTYifQ\", \"payload\": \"\", \"signature\": \"ZW5jb2RlZF9zaWduYXR1cmU=\"}'\n        e_result = (\n            True,\n            None,\n            {\n                \"nonce\": \"26e6a46efadd477d908d7c2014e49b34\",\n                \"url\": \"http://laptop.nclm-samba.local/acme/authz/PG189FFzfao1\",\n                \"alg\": \"RS256\",\n                \"kid\": \"http://laptop.nclm-samba.local/acme/acct/yuZ1GUJb76Zk\",\n            },\n            {},\n            b\"encoded_signature\",\n        )\n        self.assertEqual(e_result, self.decode_message(self.logger, data_dic))\n\n    def test_039_helper_decode_message(self):\n        \"\"\"decode message with empty payload - certbot issue\"\"\"\n        data_dic = '{\"protected\": \"eyJub25jZSI6ICIyNmU2YTQ2ZWZhZGQ0NzdkOTA4ZDdjMjAxNGU0OWIzNCIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYXV0aHovUEcxODlGRnpmYW8xIiwgImtpZCI6ICJodHRwOi8vbGFwdG9wLm5jbG0tc2FtYmEubG9jYWwvYWNtZS9hY2N0L3l1WjFHVUpiNzZaayIsICJhbGciOiAiUlMyNTYifQ\", \"payload\": \"eyJmb28iOiAiYmFyMSJ9\", \"signature\": \"ZW5jb2RlZF9zaWduYXR1cmU=\"}'\n        e_result = (\n            True,\n            None,\n            {\n                \"nonce\": \"26e6a46efadd477d908d7c2014e49b34\",\n                \"url\": \"http://laptop.nclm-samba.local/acme/authz/PG189FFzfao1\",\n                \"alg\": \"RS256\",\n                \"kid\": \"http://laptop.nclm-samba.local/acme/acct/yuZ1GUJb76Zk\",\n            },\n            {\"foo\": \"bar1\"},\n            b\"encoded_signature\",\n        )\n        self.assertEqual(e_result, self.decode_message(self.logger, data_dic))\n\n    @patch(\"json.loads\")\n    def test_040_helper_decode_message(self, mock_json):\n        \"\"\"decode message with with exception during decoding\"\"\"\n        mock_json.side_effect = Exception(\"exc_mock_json\")\n        data_dic = '{\"protected\": \"eyJub25jZSI6ICIyNmU2YTQ2ZWZhZGQ0NzdkOTA4ZDdjMjAxNGU0OWIzNCIsICJ1cmwiOiAiaHR0cDovL2xhcHRvcC5uY2xtLXNhbWJhLmxvY2FsL2FjbWUvYXV0aHovUEcxODlGRnpmYW8xIiwgImtpZCI6ICJodHRwOi8vbGFwdG9wLm5jbG0tc2FtYmEubG9jYWwvYWNtZS9hY2N0L3l1WjFHVUpiNzZaayIsICJhbGciOiAiUlMyNTYifQ\", \"payload\": \"\", \"signature\": \"ZW5jb2RlZF9zaWduYXR1cmU=\"}'\n        if int(\"%i%i\" % (sys.version_info[0], sys.version_info[1])) < 37:\n            result = \"ERROR:test_a2c:Error during message decoding Invalid JWS Object [Invalid format]\"\n            e_result = (False, \"Invalid JWS Object [Invalid format]\", {}, {}, None)\n        else:\n            result = \"ERROR:test_a2c:Error during message decoding Invalid JWS Object [Invalid format]\"\n            e_result = (False, \"Invalid JWS Object [Invalid format]\", {}, {}, None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(e_result, self.decode_message(self.logger, data_dic))\n        self.assertIn(result, lcm.output)\n\n    def test_041_helper_cert_serial_get(self):\n        \"\"\"test cert_serial_get\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(10, self.cert_serial_get(self.logger, cert))\n\n    def test_042_helper_cert_serial_get(self):\n        \"\"\"test cert_serial_get\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(10, self.cert_serial_get(self.logger, cert, hexformat=False))\n\n    def test_043_helper_cert_serial_get(self):\n        \"\"\"test cert_serial_get\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(\"0a\", self.cert_serial_get(self.logger, cert, hexformat=True))\n\n    def test_044_helper_cert_issuer_get(self):\n        \"\"\"test cert_issuer_get\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(\"CN=foo.example.com\", self.cert_issuer_get(self.logger, cert))\n\n    def test_045_helper_cert_san_get(self):\n        \"\"\"test cert_san_get for a single SAN\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual([\"DNS:foo.example.com\"], self.cert_san_get(self.logger, cert))\n\n    def test_046_helper_cert_san_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDIzCCAgugAwIBAgICBZgwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAxMPZm9v\n                LmV4YW1wbGUuY29tMB4XDTE5MDEyMDE3MDkxMVoXDTE5MDIxOTE3MDkxMVowGjEY\n                MBYGA1UEAxMPZm9vLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n                MIIBCgKCAQEA+EM+gzAyjegQSRbJI+qZJhuAGM9i48xvIfuOQHleXoJPjV+8VZRV\n                KDljZNXdNT5Zi7K6HY9C622NOV7QefB6zTtm6mSY08ypNsaeorhIvJdnpaJ9gAGH\n                YeQqJ04fL099kiRXJAv8gT8wdpiekg2KEU4wlXMIRfSHiiB37yjcqUzXl6XYYKGe\n                2USMpDfliXL3o8TW2KByGUdCzXUdNbMgzRXwYxkX2+xV2f0vn8NyXHiHg9yJRof2\n                HTjyvAcXN5Nr987slq/Ex5lXLtpB861Ov3ZbwxyzREjmreZBlze7KTfP5IY66XuN\n                Mvhi7AAs0cLTd3SNjpppE/yvUi5q5gfhXQIDAQABo3MwcTAfBgNVHSMEGDAWgBSl\n                YnpKQw12MmEMpvsTEeQi17UsnDAdBgNVHQ4EFgQUpWJ6SkMNdjJhDKb7ExHkIte1\n                LJwwLwYDVR0RBCgwJoIRZm9vLTIuZXhhbXBsZS5jb22CEWZvby0xLmV4YW1wbGUu\n                Y29tMA0GCSqGSIb3DQEBCwUAA4IBAQASA20TtMPXIHH10dikLhFuI14EOtZzXvCx\n                kGlJw9/5JuvVKLsL1wd8BC9o/lg8apDqsrDZ/+0Nc8g3Z9HRN99vcLsVDdT27DkM\n                BslfXdN/qBhKAp3m7jw29uijX5fss+Wz9iHfHciUjVyMJ4DoFxHYPbMWQG8XEUKR\n                TP6Gp79DzCiPKFt52Y8yVikIET4fnyRzU8kGKLuPoIt+EQQzpG26qWAjeNHAASEM\n                keiA+tedMWzydX52B+tGg+l2svxg34apIBDjK8pF+8ZxTt5yjVUa10GbpffJuiEh\n                NWQddOR8IHg+v6lWc9BtuuKK5ubsg6XOiEjhhr42AKViKalX1i4+\"\"\"\n        self.assertEqual(\n            [\"DNS:foo-2.example.com\", \"DNS:foo-1.example.com\"],\n            self.cert_san_get(self.logger, cert),\n        )\n\n    def test_047_helper_cert_san_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDaDCCAVCgAwIBAgIICwL0UBNcUakwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMzA3MTkxODU5NDRaFw0yNDA3MTgxODU5NDRaMBkxFzAVBgNVBAMTDjE5Mi4xNjguMTQuMTMxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEN626lPpwBt4SEvdf5Tb0BpP1tl9KiFE/9xCIyYPsi9VXVDq/EcwO3CRp4fy+3bhZj6i43DdnluETcx8ZR2XyE6NuMGwwHQYDVR0OBBYEFBp+ZupvT2BB92sDkxy2GffHXDRLMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAsGA1UdDwQEAwID6DAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBMCoDoMwDQYJKoZIhvcNAQELBQADggIBAFpq5RWGP4kDRnRjq8pte87bGS9LEmSlGOA8HlQZ+kjAoTunNN7/gvDch4F/CIl1N8cbQ/Ty1vx9CznTpQ39c2LNMILnjNHqQpYRIgLSBvCm26pAdlmicy6zdGlRKaePoMXINw4csDZ4REERg/c21ANhFclYyWWUM987bHZuBZJM8zBfR98ZnOzuQMRb5xztRlXSvddW4qEyKihl+5wPduaF8hDui4wbDFW6pUE9DWO/S1m37Tshh1O3NLlAlaMMwLsYaGkW7yzM4OrzmghJCRtdF9lbYYqHoKxLVWyCRF/pXqqQ/y+k4sN0MeZ7Wk4dI18aGHTGEzu6GSynNptyCQNsoTYexDA/rx57ukX7TqrU5JU/VyrKYD+M/rsLMj3vY4YmmH4W12IhAxa6+UmGG9ixHKpTgLVLRJDdzPMLY+IdI9WHdo7nHDOsaKvrFWqmvsCxT214jN0fVkOTMazG4ILg4DZhMWh8QxGULR7ul2oYnlyGUXiag7qLjNu1/RltJg9sp+ZxVC7RWaoCwxp6CIT95wrUAFTt9NBkccafsQKsF2ZtrUNZ8Z7B3y6hzr9d6rWlZCKlcr/ZNSOnrRTwuCz5HL3Gd2/DfyZUmy5U1+URbktMIdddlV5jaeSpwFZI8Xga4cYJAE7xjVq8HN3jbZ6m4PyylfaQfXisozKRECs4\"\"\"\n        self.assertEqual([\"IP:192.168.14.131\"], self.cert_san_get(self.logger, cert))\n\n    def test_048_helper_cert_san_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDezCCAWOgAwIBAgIIIAuZLppuFT4wDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMzA3MjAwNDIxMTlaFw0yNDA3MTkwNDIxMTlaMBkxFzAVBgNVBAMTDjE5Mi4xNjguMTQuMTMxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQTs6Bra1zfVSiReD4AYj8HCKdcaMO5WsgB0zhpVu3HuSQSIQHC8CMe8haCywjYisbbWeDzT654tc674/MjScraOBgDB+MB0GA1UdDgQWBBQQa+M+3oTsdKTSB/Rt3Dk7/Vy0YzAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TALBgNVHQ8EBAMCA+gwDAYDVR0TAQH/BAIwADAhBgNVHREEGjAYghBmb29iYXIuYmFyLmxvY2FshwTAqA6DMA0GCSqGSIb3DQEBCwUAA4ICAQCcevAczULbl5Le/xI1LSQ/PSsROjOZHUjlWf5bRs53aTM6wMqDBsFGdLzTN5vzWqVjie1Nzu8XGSEEuF0L/2bltGgYiYQqD4HKJedEEbYxQbg77o9JLp52MltvXGRH5gYGSGPZbuQ8QANvDn6FqBZjskOtED8SZGGt5spgxK7eguoJoQken68TgdZptL6l6eryTgouPbG0j5vTPPxuZpqxM9vQa4ADyqyvOKRMkZC98IbruChlCtFztILJPkvNx8Gbmlzv201uW9/9mNzcV8vVtlcB+Ftb/+sCfYuU/ShwUuOxOLE7+OKjLlalfniNwqx2l6f30nvvsa11vQc/Rwy1Z+vv96EzyF+GthMx2qLIG4eLLbISATwUfpR0UcLMtr83LRzB578rxrtwcgB5s+AWSDsYEKnzXabQdX1cEuiM3iEdlZ7McFzRvwElObhoDDOqOjGALWmdboox6dDskpQEhe6JALsj3mH07017h5T3W3PvqWD2IAsqH+WTuxCTmfjbqqoAz/Zt2ipIAFtSk79WvWwth/K+xtYhmuoe2+ygocqa9tF9AyoihImSEk1EjXvqKqRLPZwg41C3WKvLlg57fpRFZYR1W28ZqAqqVNf8MMHcsHdZ7koMBhIKKnSe/HdLWm7ghVjAEdYVYvOcOZHzxXBmnV/6ZLRQXu2XQnATJw==\"\"\"\n        self.assertEqual(\n            [\"DNS:foobar.bar.local\", \"IP:192.168.14.131\"],\n            self.cert_san_get(self.logger, cert),\n        )\n\n    def test_049_helper_cert_san_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a single SAN\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(\n            [\"DNS:foo.example.com\"], self.cert_san_pyopenssl_get(self.logger, cert)\n        )\n\n    def test_050_cert_san_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDIzCCAgugAwIBAgICBZgwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAxMPZm9v\n                LmV4YW1wbGUuY29tMB4XDTE5MDEyMDE3MDkxMVoXDTE5MDIxOTE3MDkxMVowGjEY\n                MBYGA1UEAxMPZm9vLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n                MIIBCgKCAQEA+EM+gzAyjegQSRbJI+qZJhuAGM9i48xvIfuOQHleXoJPjV+8VZRV\n                KDljZNXdNT5Zi7K6HY9C622NOV7QefB6zTtm6mSY08ypNsaeorhIvJdnpaJ9gAGH\n                YeQqJ04fL099kiRXJAv8gT8wdpiekg2KEU4wlXMIRfSHiiB37yjcqUzXl6XYYKGe\n                2USMpDfliXL3o8TW2KByGUdCzXUdNbMgzRXwYxkX2+xV2f0vn8NyXHiHg9yJRof2\n                HTjyvAcXN5Nr987slq/Ex5lXLtpB861Ov3ZbwxyzREjmreZBlze7KTfP5IY66XuN\n                Mvhi7AAs0cLTd3SNjpppE/yvUi5q5gfhXQIDAQABo3MwcTAfBgNVHSMEGDAWgBSl\n                YnpKQw12MmEMpvsTEeQi17UsnDAdBgNVHQ4EFgQUpWJ6SkMNdjJhDKb7ExHkIte1\n                LJwwLwYDVR0RBCgwJoIRZm9vLTIuZXhhbXBsZS5jb22CEWZvby0xLmV4YW1wbGUu\n                Y29tMA0GCSqGSIb3DQEBCwUAA4IBAQASA20TtMPXIHH10dikLhFuI14EOtZzXvCx\n                kGlJw9/5JuvVKLsL1wd8BC9o/lg8apDqsrDZ/+0Nc8g3Z9HRN99vcLsVDdT27DkM\n                BslfXdN/qBhKAp3m7jw29uijX5fss+Wz9iHfHciUjVyMJ4DoFxHYPbMWQG8XEUKR\n                TP6Gp79DzCiPKFt52Y8yVikIET4fnyRzU8kGKLuPoIt+EQQzpG26qWAjeNHAASEM\n                keiA+tedMWzydX52B+tGg+l2svxg34apIBDjK8pF+8ZxTt5yjVUa10GbpffJuiEh\n                NWQddOR8IHg+v6lWc9BtuuKK5ubsg6XOiEjhhr42AKViKalX1i4+\"\"\"\n        self.assertEqual(\n            [\"DNS:foo-2.example.com\", \"DNS:foo-1.example.com\"],\n            self.cert_san_pyopenssl_get(self.logger, cert),\n        )\n\n    def test_051_helper_cert_san_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDaDCCAVCgAwIBAgIICwL0UBNcUakwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMzA3MTkxODU5NDRaFw0yNDA3MTgxODU5NDRaMBkxFzAVBgNVBAMTDjE5Mi4xNjguMTQuMTMxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEN626lPpwBt4SEvdf5Tb0BpP1tl9KiFE/9xCIyYPsi9VXVDq/EcwO3CRp4fy+3bhZj6i43DdnluETcx8ZR2XyE6NuMGwwHQYDVR0OBBYEFBp+ZupvT2BB92sDkxy2GffHXDRLMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAsGA1UdDwQEAwID6DAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBMCoDoMwDQYJKoZIhvcNAQELBQADggIBAFpq5RWGP4kDRnRjq8pte87bGS9LEmSlGOA8HlQZ+kjAoTunNN7/gvDch4F/CIl1N8cbQ/Ty1vx9CznTpQ39c2LNMILnjNHqQpYRIgLSBvCm26pAdlmicy6zdGlRKaePoMXINw4csDZ4REERg/c21ANhFclYyWWUM987bHZuBZJM8zBfR98ZnOzuQMRb5xztRlXSvddW4qEyKihl+5wPduaF8hDui4wbDFW6pUE9DWO/S1m37Tshh1O3NLlAlaMMwLsYaGkW7yzM4OrzmghJCRtdF9lbYYqHoKxLVWyCRF/pXqqQ/y+k4sN0MeZ7Wk4dI18aGHTGEzu6GSynNptyCQNsoTYexDA/rx57ukX7TqrU5JU/VyrKYD+M/rsLMj3vY4YmmH4W12IhAxa6+UmGG9ixHKpTgLVLRJDdzPMLY+IdI9WHdo7nHDOsaKvrFWqmvsCxT214jN0fVkOTMazG4ILg4DZhMWh8QxGULR7ul2oYnlyGUXiag7qLjNu1/RltJg9sp+ZxVC7RWaoCwxp6CIT95wrUAFTt9NBkccafsQKsF2ZtrUNZ8Z7B3y6hzr9d6rWlZCKlcr/ZNSOnrRTwuCz5HL3Gd2/DfyZUmy5U1+URbktMIdddlV5jaeSpwFZI8Xga4cYJAE7xjVq8HN3jbZ6m4PyylfaQfXisozKRECs4\"\"\"\n        self.assertEqual(\n            [\"IP Address:192.168.14.131\"],\n            self.cert_san_pyopenssl_get(self.logger, cert),\n        )\n\n    def test_052_helper_cert_san_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDezCCAWOgAwIBAgIIIAuZLppuFT4wDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMzA3MjAwNDIxMTlaFw0yNDA3MTkwNDIxMTlaMBkxFzAVBgNVBAMTDjE5Mi4xNjguMTQuMTMxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQTs6Bra1zfVSiReD4AYj8HCKdcaMO5WsgB0zhpVu3HuSQSIQHC8CMe8haCywjYisbbWeDzT654tc674/MjScraOBgDB+MB0GA1UdDgQWBBQQa+M+3oTsdKTSB/Rt3Dk7/Vy0YzAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TALBgNVHQ8EBAMCA+gwDAYDVR0TAQH/BAIwADAhBgNVHREEGjAYghBmb29iYXIuYmFyLmxvY2FshwTAqA6DMA0GCSqGSIb3DQEBCwUAA4ICAQCcevAczULbl5Le/xI1LSQ/PSsROjOZHUjlWf5bRs53aTM6wMqDBsFGdLzTN5vzWqVjie1Nzu8XGSEEuF0L/2bltGgYiYQqD4HKJedEEbYxQbg77o9JLp52MltvXGRH5gYGSGPZbuQ8QANvDn6FqBZjskOtED8SZGGt5spgxK7eguoJoQken68TgdZptL6l6eryTgouPbG0j5vTPPxuZpqxM9vQa4ADyqyvOKRMkZC98IbruChlCtFztILJPkvNx8Gbmlzv201uW9/9mNzcV8vVtlcB+Ftb/+sCfYuU/ShwUuOxOLE7+OKjLlalfniNwqx2l6f30nvvsa11vQc/Rwy1Z+vv96EzyF+GthMx2qLIG4eLLbISATwUfpR0UcLMtr83LRzB578rxrtwcgB5s+AWSDsYEKnzXabQdX1cEuiM3iEdlZ7McFzRvwElObhoDDOqOjGALWmdboox6dDskpQEhe6JALsj3mH07017h5T3W3PvqWD2IAsqH+WTuxCTmfjbqqoAz/Zt2ipIAFtSk79WvWwth/K+xtYhmuoe2+ygocqa9tF9AyoihImSEk1EjXvqKqRLPZwg41C3WKvLlg57fpRFZYR1W28ZqAqqVNf8MMHcsHdZ7koMBhIKKnSe/HdLWm7ghVjAEdYVYvOcOZHzxXBmnV/6ZLRQXu2XQnATJw==\"\"\"\n        self.assertEqual(\n            [\"DNS:foobar.bar.local\", \"IP Address:192.168.14.131\"],\n            self.cert_san_pyopenssl_get(self.logger, cert),\n        )\n\n    def test_053_helper_cert_san_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a single SAN\"\"\"\n        cert = \"\"\"\n-----BEGIN CERTIFICATE-----\nMIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\nZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\nFgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\nHVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\ndpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\nFyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\nzuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\nLjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\nAANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\nTW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\nbta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\nfxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\nt+eRUDECE+0UnjyeCjTn3EU=\n-----END CERTIFICATE-----\n                \"\"\"\n        self.assertEqual(\n            [\"DNS:foo.example.com\"],\n            self.cert_san_pyopenssl_get(self.logger, cert, recode=False),\n        )\n\n    def test_054_helper_cert_san_get(self):\n        \"\"\"test cert_san_get for a single SAN and recode = False\"\"\"\n        cert = \"\"\"-----BEGIN X509 CERTIFICATE-----\nMIIE2zCCAsOgAwIBAgIPAXI102H4bCWEkhD2SaLsMA0GCSqGSIb3DQEBDQUAMDIx\nCzAJBgNVBAYTAkRFMQ4wDAYDVQQKEwVOb2tpYTETMBEGA1UEAwwKbmNtX3N1Yl9j\nYTAeFw0yMDA1MjEwNTMyMjVaFw0yMDA2MjAyMzU5NTlaMBkxFzAVBgNVBAMTDmZv\nbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+\nz+9wsEewBf1hnk3yAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN\n+lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOL\nhjTlN2f7Z081XWIgkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MF\no+tLu0toAc0nm9v8Otme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgba\nj94e1uznsyni7CYw/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xo\naygpd9+UHCREhcOu/wIDAQABo4IBBTCCAQEwHwYDVR0jBBgwFoAUEZ+5Dp2l8KCZ\nzHhwr3965P6xxsswHQYDVR0OBBYEFKsuSjgZZMl9vZeBB0wks4Wbg4PhMAsGA1Ud\nDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDBUBgNVHR8ETTBLMEmg\nR6BFhkNodHRwOi8vc3J2Lm5jbG0tc2FtYmEubG9jYWw6ODA4MC9jcmwtYXMtZGVy\nL2N1cnJlbnRjcmwtODcuY3JsP2lkPTg3MEEGCCsGAQUFBwEBBDUwMzAxBggrBgEF\nBQcwAYYlaHR0cDovL3Nydi5uY2xtLXNhbWJhLmxvY2FsOjgwOTAvY2EyLzANBgkq\nhkiG9w0BAQ0FAAOCAgEAgpfWOM8fWlkwSbWtgnAKnu8GNMteOckWS1gMydOhokhY\nPZdkpL8uoMWRahyjhmAH85TtHdydVaQ9NNBUTsbiOqkN2jPurDdzgfUs2gAwoR05\nMkHVWI1+C3lHAVlqPWYld+6Kl3lnEjy3jFSMugTuq5h79f0KxGle7W568Xg+zI3R\nRy1dRggR6W2G9L+7Ez8Y+H/8P/gjbTO1GGYoXI4ISQl3EinL/X7XpYnQ3o14uDLb\nm/h+YyLfi03m8tLJQPM7soDAZx6qI/1V4H/VT1VEKBCiec8w580rIH6GSrjUkddp\nwd0p74B8xwmt9zA+gBV3Js72PBy9mdcMIvYIO3otmN2jQL8PC1B8VNEmf0l8a5wq\n07qftQEI82vcrLG8Dgy7R9AxrIxd1xnZOTrcOo3dU+blAehAJZWT2B0B8XyoGk2/\nCiMCwOQijMgp97tjnuQ3dkRhu50kUN5LCa9jU2ongXj0+28mEKZ5rAQUBQmAMITR\nhTkTB1OxdpFMxyg83OZdYu/xit9YfVB0AAyarqjTst/y79UkExfEf0sAARBiffkx\nPZwtZpoz736yvIqanX6u2zUHLDzSRZXOZHY6pxANqoH6howxqGkI3FMjeDbDUln7\n/TEtRju77ONV1X+8iPYrnQqTRoR3a3IwT8Cz/HErNM6aNCvPVPqakZXZrcpXILY=\n-----END X509 CERTIFICATE-----\"\"\"\n\n        self.assertEqual(\n            [\"DNS:foo1.bar.local\"], self.cert_san_get(self.logger, cert, recode=False)\n        )\n\n    @patch(\"acme_srv.helpers.certificates.cert_load\")\n    def test_055_helper_cert_san_get(self, mock_certload):\n        \"\"\"test cert_san_get for a single SAN and recode = False\"\"\"\n        cert = \"cert\"\n        mock_certload.return_value = \"mock_csrload\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cert_san_get(self.logger, cert, recode=False))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting SANs from certificate: 'str' object has no attribute 'extensions'\",\n            lcm.output,\n        )\n\n    def test_056_helper_build_pem_file(self):\n        \"\"\"test build_pem_file without exsting content\"\"\"\n        existing = None\n        cert = \"cert\"\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\ncert\\n-----END CERTIFICATE-----\\n\",\n            self.build_pem_file(self.logger, existing, cert, True),\n        )\n\n    def test_057_helper_build_pem_file(self):\n        \"\"\"test build_pem_file with exsting content\"\"\"\n        existing = \"existing\"\n        cert = \"cert\"\n        self.assertEqual(\n            \"existing-----BEGIN CERTIFICATE-----\\ncert\\n-----END CERTIFICATE-----\\n\",\n            self.build_pem_file(self.logger, existing, cert, True),\n        )\n\n    def test_058_helper_build_pem_file(self):\n        \"\"\"test build_pem_file with long cert (to test wrap)\"\"\"\n        existing = None\n        cert = (\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n        )\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\naaaaaaaaa\\n-----END CERTIFICATE-----\\n\",\n            self.build_pem_file(self.logger, existing, cert, True),\n        )\n\n    def test_059_helper_build_pem_file(self):\n        \"\"\"test build_pem_file with long cert (to test wrap)\"\"\"\n        existing = None\n        cert = (\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n        )\n        self.assertEqual(\n            \"-----BEGIN CERTIFICATE-----\\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\n-----END CERTIFICATE-----\\n\",\n            self.build_pem_file(self.logger, existing, cert, False),\n        )\n\n    def test_060_helper_build_pem_file(self):\n        \"\"\"test build_pem_file with long cert (to test wrap)\"\"\"\n        existing = \"existing\"\n        cert = (\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n        )\n        self.assertEqual(\n            \"existing-----BEGIN CERTIFICATE-----\\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\n-----END CERTIFICATE-----\\n\",\n            self.build_pem_file(self.logger, existing, cert, False),\n        )\n\n    def test_061_helper_build_pem_file(self):\n        \"\"\"test build_pem_file for CSR\"\"\"\n        existing = None\n        csr = \"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\n        result = \"\"\"-----BEGIN CERTIFICATE REQUEST-----\nMIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CT\nZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDg\nWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4\nFtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZb\neI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY\n9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3\nBgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJh\nci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7\nn66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAt\niUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYu\ntUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9I\nNJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQs\nKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\n-----END CERTIFICATE REQUEST-----\n\"\"\"\n        self.assertEqual(\n            result, self.build_pem_file(self.logger, existing, csr, False, True)\n        )\n\n    def test_062_helper_b64_decode(self):\n        \"\"\"test bas64 decoder for string value\"\"\"\n        self.assertEqual(\"test\", self.b64_decode(self.logger, \"dGVzdA==\"))\n\n    def test_063_helper_b64_decode(self):\n        \"\"\"test bas64 decoder for byte value\"\"\"\n        self.assertEqual(\"test\", self.b64_decode(self.logger, b\"dGVzdA==\"))\n\n    def test_064_helper_date_to_datestr(self):\n        \"\"\"convert dateobj to date-string with default format\"\"\"\n        self.assertEqual(\n            \"2019-10-27T00:00:00Z\", self.date_to_datestr(datetime.date(2019, 10, 27))\n        )\n\n    def test_065_helper_date_to_datestr(self):\n        \"\"\"convert dateobj to date-string with a predefined format\"\"\"\n        self.assertEqual(\n            \"2019.10.27\", self.date_to_datestr(datetime.date(2019, 10, 27), \"%Y.%m.%d\")\n        )\n\n    def test_066_helper_date_to_datestr(self):\n        \"\"\"convert dateobj to date-string for an knvalid date\"\"\"\n        self.assertEqual(None, self.date_to_datestr(\"foo\", \"%Y.%m.%d\"))\n\n    def test_067_helper_datestr_to_date(self):\n        \"\"\"convert datestr to date with default format\"\"\"\n        self.assertEqual(\n            datetime.datetime(2019, 11, 27, 0, 1, 2),\n            self.datestr_to_date(\"2019-11-27T00:01:02\"),\n        )\n\n    def test_068_helper_datestr_to_date(self):\n        \"\"\"convert datestr to date with predefined format\"\"\"\n        self.assertEqual(\n            datetime.datetime(2019, 11, 27, 0, 0, 0),\n            self.datestr_to_date(\"2019.11.27\", \"%Y.%m.%d\"),\n        )\n\n    def test_069_helper_datestr_to_date(self):\n        \"\"\"convert datestr to date with invalid format\"\"\"\n        self.assertEqual(None, self.datestr_to_date(\"foo\", \"%Y.%m.%d\"))\n\n    def test_070_helper_dkeys_lower(self):\n        \"\"\"dkeys_lower with a simple string\"\"\"\n        tree = \"fOo\"\n        self.assertEqual(\"fOo\", self.dkeys_lower(tree))\n\n    def test_071_helper_dkeys_lower(self):\n        \"\"\"dkeys_lower with a simple list\"\"\"\n        tree = [\"fOo\", \"bAr\"]\n        self.assertEqual([\"fOo\", \"bAr\"], self.dkeys_lower(tree))\n\n    def test_072_helper_dkeys_lower(self):\n        \"\"\"dkeys_lower with a simple dictionary\"\"\"\n        tree = {\"kEy\": \"vAlUe\"}\n        self.assertEqual({\"key\": \"vAlUe\"}, self.dkeys_lower(tree))\n\n    def test_073_helper_dkeys_lower(self):\n        \"\"\"dkeys_lower with a nested dictionary containg strings, list and dictionaries\"\"\"\n        tree = {\n            \"kEy1\": \"vAlUe2\",\n            \"keys2\": [\"lIsT2\", {\"kEyS3\": \"vAlUe3\", \"kEyS4\": \"vAlUe3\"}],\n            \"keys4\": {\"kEyS4\": \"vAluE5\", \"kEyS5\": \"vAlUE6\"},\n        }\n        self.assertEqual(\n            {\n                \"key1\": \"vAlUe2\",\n                \"keys2\": [\"lIsT2\", {\"keys3\": \"vAlUe3\", \"keys4\": \"vAlUe3\"}],\n                \"keys4\": {\"keys5\": \"vAlUE6\", \"keys4\": \"vAluE5\"},\n            },\n            self.dkeys_lower(tree),\n        )\n\n    def test_074_helper_cert_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        cert = \"\"\"\n-----BEGIN X509 CERTIFICATE-----\nMIIE2zCCAsOgAwIBAgIPAXI102H4bCWEkhD2SaLsMA0GCSqGSIb3DQEBDQUAMDIx\nCzAJBgNVBAYTAkRFMQ4wDAYDVQQKEwVOb2tpYTETMBEGA1UEAwwKbmNtX3N1Yl9j\nYTAeFw0yMDA1MjEwNTMyMjVaFw0yMDA2MjAyMzU5NTlaMBkxFzAVBgNVBAMTDmZv\nbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+\nz+9wsEewBf1hnk3yAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN\n+lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOL\nhjTlN2f7Z081XWIgkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MF\no+tLu0toAc0nm9v8Otme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgba\nj94e1uznsyni7CYw/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xo\naygpd9+UHCREhcOu/wIDAQABo4IBBTCCAQEwHwYDVR0jBBgwFoAUEZ+5Dp2l8KCZ\nzHhwr3965P6xxsswHQYDVR0OBBYEFKsuSjgZZMl9vZeBB0wks4Wbg4PhMAsGA1Ud\nDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDBUBgNVHR8ETTBLMEmg\nR6BFhkNodHRwOi8vc3J2Lm5jbG0tc2FtYmEubG9jYWw6ODA4MC9jcmwtYXMtZGVy\nL2N1cnJlbnRjcmwtODcuY3JsP2lkPTg3MEEGCCsGAQUFBwEBBDUwMzAxBggrBgEF\nBQcwAYYlaHR0cDovL3Nydi5uY2xtLXNhbWJhLmxvY2FsOjgwOTAvY2EyLzANBgkq\nhkiG9w0BAQ0FAAOCAgEAgpfWOM8fWlkwSbWtgnAKnu8GNMteOckWS1gMydOhokhY\nPZdkpL8uoMWRahyjhmAH85TtHdydVaQ9NNBUTsbiOqkN2jPurDdzgfUs2gAwoR05\nMkHVWI1+C3lHAVlqPWYld+6Kl3lnEjy3jFSMugTuq5h79f0KxGle7W568Xg+zI3R\nRy1dRggR6W2G9L+7Ez8Y+H/8P/gjbTO1GGYoXI4ISQl3EinL/X7XpYnQ3o14uDLb\nm/h+YyLfi03m8tLJQPM7soDAZx6qI/1V4H/VT1VEKBCiec8w580rIH6GSrjUkddp\nwd0p74B8xwmt9zA+gBV3Js72PBy9mdcMIvYIO3otmN2jQL8PC1B8VNEmf0l8a5wq\n07qftQEI82vcrLG8Dgy7R9AxrIxd1xnZOTrcOo3dU+blAehAJZWT2B0B8XyoGk2/\nCiMCwOQijMgp97tjnuQ3dkRhu50kUN5LCa9jU2ongXj0+28mEKZ5rAQUBQmAMITR\nhTkTB1OxdpFMxyg83OZdYu/xit9YfVB0AAyarqjTst/y79UkExfEf0sAARBiffkx\nPZwtZpoz736yvIqanX6u2zUHLDzSRZXOZHY6pxANqoH6howxqGkI3FMjeDbDUln7\n/TEtRju77ONV1X+8iPYrnQqTRoR3a3IwT8Cz/HErNM6aNCvPVPqakZXZrcpXILY=\n-----END X509 CERTIFICATE-----\"\"\"\n\n        pub_key = \"\"\"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3y\nAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97\nhN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIg\nkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8\nOtme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw\n/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu\n/wIDAQAB\n-----END PUBLIC KEY-----\n\"\"\"\n        self.assertEqual(pub_key, self.cert_pubkey_get(self.logger, cert))\n\n    def test_075_helper_csr_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        csr = \"\"\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\"\"\n\n        pub_key = \"\"\"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3y\nAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97\nhN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIg\nkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8\nOtme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw\n/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu\n/wIDAQAB\n-----END PUBLIC KEY-----\n\"\"\"\n        self.assertEqual(pub_key, self.csr_pubkey_get(self.logger, csr))\n\n    def test_076_helper_csr_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        csr = \"\"\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\"\"\n\n        pub_key = \"\"\"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3y\nAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97\nhN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIg\nkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8\nOtme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw\n/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu\n/wIDAQAB\n-----END PUBLIC KEY-----\n\"\"\"\n        self.assertEqual(pub_key, self.csr_pubkey_get(self.logger, csr, encoding=\"pem\"))\n\n    def test_077_helper_csr_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        csr = \"\"\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\"\"\n        pub_key = \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3yAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIgkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8Otme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu/wIDAQAB\"\n        self.assertEqual(\n            pub_key, self.csr_pubkey_get(self.logger, csr, encoding=\"base64der\")\n        )\n\n    def test_078_helper_csr_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        csr = \"\"\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\"\"\n        pub_key = b\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3yAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIgkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8Otme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu/wIDAQAB\"\n        self.assertEqual(\n            pub_key,\n            base64.b64encode(self.csr_pubkey_get(self.logger, csr, encoding=\"der\")),\n        )\n\n    def test_079_helper_csr_pubkey_get(self):\n        \"\"\"test get public_key from certificate\"\"\"\n        csr = \"\"\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBvH7P73CwR7AF/WGeTfIDLlMWD6VZV3CTZBF0AwNMTFU/zbdAX8r63pzElX/5C5ZVsc36XHqdAJcioJlI33uE3RhOSvDyOcDgWlnPK9gj2soQ7enizGqd1u7hf6C3IwFtc4uGNOU3Z/tnTzVdYiCSKS+5lTZfMxn4FtEUN+w90NHBvC+AlTo3Gl0gqbYOZgg/UwWj60u7S2gBzSeb2/w62Z7bz+SknGZbeI4ySo30ET6oCSCAUN42jE+1dHI/Y+tGBtqP3h7W7OezKeLsJjD9r07U0+uMoVCY9oKTyT0gK8+gsde0tpt6QKa93HJGUPAP9ehrKCl335QcJESFw67/AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAf4cdGpYHLqX+06BFF7+NqXLmKvc7n66vAfevLN75eu/pCXhhRSdpXvcYm+mAVEXJCPaG2kFGt6wfBvVWoVX/91d+OuAtiUHmhY95Oi7g3RF3ThCrvT2mR4zsNiKgC34jXbl9489iIiFRBQXkq2fLwN5JwBYutUENwkDIeApRRbmUzTDbar1xoBAQ3GjVtOAEjHc/3S1yyKkCpM6Qkg8uWOJAXw9INJqH6x55nMZrvTUuXkURc/mvhV+bp2vdKoigGvfa3VVfoAI0BZLQMohQ9QLKoNQsKxEs3JidvpZrl3o23LMGEPoJs3zIuowTa217PHwdBw4UwtD7KxJK/+344A==\"\"\"\n        pub_key = b\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx+z+9wsEewBf1hnk3yAy5TFg+lWVdwk2QRdAMDTExVP823QF/K+t6cxJV/+QuWVbHN+lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X+gtyMBbXOLhjTlN2f7Z081XWIgkikvuZU2XzMZ+BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo+tLu0toAc0nm9v8Otme28/kpJxmW3iOMkqN9BE+qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw/a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD/Xoaygpd9+UHCREhcOu/wIDAQAB\"\n        self.assertFalse(self.csr_pubkey_get(self.logger, csr, encoding=\"unk\"))\n\n    def test_080_helper_convert_byte_to_string(self):\n        \"\"\"convert byte2string for a string value\"\"\"\n        self.assertEqual(\"foo\", self.convert_byte_to_string(\"foo\"))\n\n    def test_081_helper_convert_byte_to_string(self):\n        \"\"\"convert byte2string for a string value\"\"\"\n        self.assertEqual(\"foo\", self.convert_byte_to_string(\"foo\"))\n\n    def test_082_helper_convert_byte_to_string(self):\n        \"\"\"convert byte2string for a string value\"\"\"\n        self.assertNotEqual(\"foo\", self.convert_byte_to_string(\"foobar\"))\n\n    def test_083_helper_convert_byte_to_string(self):\n        \"\"\"convert byte2string for a string value\"\"\"\n        self.assertNotEqual(\"foo\", self.convert_byte_to_string(b\"foobar\"))\n\n    def test_084_helper_b64_url_encode(self):\n        \"\"\"test b64_url_encode of string\"\"\"\n        self.assertEqual(b\"c3RyaW5n\", self.b64_url_encode(self.logger, \"string\"))\n\n    def test_085_helper_b64_url_encode(self):\n        \"\"\"test b64_url_encode of byte\"\"\"\n        self.assertEqual(b\"Ynl0ZQ\", self.b64_url_encode(self.logger, b\"byte\"))\n\n    def test_086_helper_csr_cn_get(self):\n        \"\"\"get cn of csr\"\"\"\n        csr = \"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0lk4lyEIa0VL/u5ic01Zo/o+gyYqFpU7xe+nbFgiKA+R1rqrzP/sR6xjHqS0Rkv/BcBXf81sp/+iDmwIQLVlBTkKdimqVHCJMAbTL8ZNpcLDaRUce4liyX1cmczPTSqI/kcyEr8tKpYN+KzvKZZsNx2Pbgu7y7/70P2uSywiW+sqYZ+X28KGFxq6wwENzJtweDVsbWql9LLtw6daF41UQg10auNlRL1nhW0SlWZh1zPPW/0sa6C3xX28jjVh843b4ekkRNLXSEYQMTi0qYR2LomQ5aTlQ/hellf17UknfN2aA2RH5D7Ek+mndj/rH21bxQg26KRmHlaJld9K1IfvJAgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAl3egrkO3I94IpxxfSJmVetd7s9uW3lSBqh9OiypFevQO7ZgUxau+k05NKTUNpSq3W9H/lRr5AG5x3/VX8XZVbcLKXQ0d6e38uXBAUFQQJmjBVYqd8KcMfqLeFFUBsLcG04yek2tNIbhXZfBtw9UYO27Y5ktMgWjAz2VskIXl3E2L0b8tGnSKDoMB07IVpYB9bHfHX4o+ccIgq1HxyYT1d+eVIQuSHHxR7j7Wkgb8RG9bCWpVWaYWKWU0Inh3gMnP06kPBJ9nOB4adgC3Hz37ab/0KpmBuQBEgmMfINwV/OpJVv2Su1FYK+uX7E1qUGae6QDsfg0Yor9uP0Vkv4b1NA==\"\n        self.assertEqual(\"foo1.bar.local\", self.csr_cn_get(self.logger, csr))\n\n    def test_087_helper_csr_cn_get(self):\n        \"\"\"get cn of csr\"\"\"\n        csr = b\"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0lk4lyEIa0VL/u5ic01Zo/o+gyYqFpU7xe+nbFgiKA+R1rqrzP/sR6xjHqS0Rkv/BcBXf81sp/+iDmwIQLVlBTkKdimqVHCJMAbTL8ZNpcLDaRUce4liyX1cmczPTSqI/kcyEr8tKpYN+KzvKZZsNx2Pbgu7y7/70P2uSywiW+sqYZ+X28KGFxq6wwENzJtweDVsbWql9LLtw6daF41UQg10auNlRL1nhW0SlWZh1zPPW/0sa6C3xX28jjVh843b4ekkRNLXSEYQMTi0qYR2LomQ5aTlQ/hellf17UknfN2aA2RH5D7Ek+mndj/rH21bxQg26KRmHlaJld9K1IfvJAgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAl3egrkO3I94IpxxfSJmVetd7s9uW3lSBqh9OiypFevQO7ZgUxau+k05NKTUNpSq3W9H/lRr5AG5x3/VX8XZVbcLKXQ0d6e38uXBAUFQQJmjBVYqd8KcMfqLeFFUBsLcG04yek2tNIbhXZfBtw9UYO27Y5ktMgWjAz2VskIXl3E2L0b8tGnSKDoMB07IVpYB9bHfHX4o+ccIgq1HxyYT1d+eVIQuSHHxR7j7Wkgb8RG9bCWpVWaYWKWU0Inh3gMnP06kPBJ9nOB4adgC3Hz37ab/0KpmBuQBEgmMfINwV/OpJVv2Su1FYK+uX7E1qUGae6QDsfg0Yor9uP0Vkv4b1NA==\"\n        self.assertEqual(\"foo1.bar.local\", self.csr_cn_get(self.logger, csr))\n\n    def test_088_helper_convert_string_to_byte(self):\n        \"\"\"convert string value to byte\"\"\"\n        value = \"foo.bar\"\n        self.assertEqual(b\"foo.bar\", self.convert_string_to_byte(value))\n\n    def test_089_helper_convert_string_to_byte(self):\n        \"\"\"convert string value to byte\"\"\"\n        value = b\"foo.bar\"\n        self.assertEqual(b\"foo.bar\", self.convert_string_to_byte(value))\n\n    def test_090_helper_convert_string_to_byte(self):\n        \"\"\"convert string value to byte\"\"\"\n        value = b\"\"\n        self.assertEqual(b\"\", self.convert_string_to_byte(value))\n\n    def test_091_helper_convert_string_to_byte(self):\n        \"\"\"convert string value to byte\"\"\"\n        value = \"\"\n        self.assertEqual(b\"\", self.convert_string_to_byte(value))\n\n    def test_092_helper_convert_string_to_byte(self):\n        \"\"\"convert string value to byte\"\"\"\n        value = None\n        self.assertFalse(self.convert_string_to_byte(value))\n\n    def test_093_helper_get_url(self):\n        \"\"\"get_url https\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"443\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"https://http_host\", self.get_url(data_dic, False))\n\n    def test_094_helper_get_url(self):\n        \"\"\"get_url http\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"80\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"http://http_host\", self.get_url(data_dic, False))\n\n    def test_095_helper_get_url(self):\n        \"\"\"get_url http wsgi.scheme\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"80\",\n            \"PATH_INFO\": \"path_info\",\n            \"wsgi.url_scheme\": \"wsgi.url_scheme\",\n        }\n        self.assertEqual(\"wsgi.url_scheme://http_host\", self.get_url(data_dic, False))\n\n    def test_096_helper_get_url(self):\n        \"\"\"get_url https include_path true bot no pathinfo\"\"\"\n        data_dic = {\"HTTP_HOST\": \"http_host\", \"SERVER_PORT\": \"443\"}\n        self.assertEqual(\"https://http_host\", self.get_url(data_dic, True))\n\n    def test_097_helper_get_url(self):\n        \"\"\"get_url https and path info\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"443\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"https://http_hostpath_info\", self.get_url(data_dic, True))\n\n    def test_098_helper_get_url(self):\n        \"\"\"get_url wsgi.url and pathinfo\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"80\",\n            \"PATH_INFO\": \"path_info\",\n            \"wsgi.url_scheme\": \"wsgi.url_scheme\",\n        }\n        self.assertEqual(\n            \"wsgi.url_scheme://http_hostpath_info\", self.get_url(data_dic, True)\n        )\n\n    def test_099_helper_get_url(self):\n        \"\"\"get_url http and pathinfo\"\"\"\n        data_dic = {\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"80\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"http://http_hostpath_info\", self.get_url(data_dic, True))\n\n    def test_100_helper_get_url(self):\n        \"\"\"get_url without hostinfo\"\"\"\n        data_dic = {\"SERVER_PORT\": \"80\", \"PATH_INFO\": \"path_info\"}\n        self.assertEqual(\"http://localhost\", self.get_url(data_dic, False))\n\n    def test_101_helper_get_url(self):\n        \"\"\"get_url without SERVER_PORT\"\"\"\n        data_dic = {\"HTTP_HOST\": \"http_host\"}\n        self.assertEqual(\"http://http_host\", self.get_url(data_dic, True))\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_102_helper_url_get(self, mock_request):\n        \"\"\"successful url get without dns servers\"\"\"\n        mock_request.return_value.text = \"foo\"\n        mock_request.return_value.status_code = 200\n        result, status_code, error_msg = self.url_get(self.logger, \"url\")\n        self.assertEqual(\"foo\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_103_helper_url_get(self, mock_request):\n        \"\"\"successful url get without dns servers\"\"\"\n        mock_request.return_value.text = \"foo\"\n        mock_request.return_value.status_code = 200\n        result, status_code, error_msg = self.url_get(\n            self.logger, \"url\", \"dns\", \"proxy\"\n        )\n        self.assertEqual(\"foo\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_104_helper_url_get(self, mock_request):\n        \"\"\"unsuccessful url get without dns servers\"\"\"\n        # this is stupid but triggrs an expeption\n        mock_request.return_value = {\"foo\": \"foo\"}\n        result, status_code, error_msg = self.url_get(self.logger, \"url\")\n        self.assertEqual(None, result)\n        self.assertEqual(500, status_code)\n        self.assertIn(\"Could not fetch URL\", error_msg)\n\n    @patch(\"acme_srv.helpers.network.url_get_with_own_dns\")\n    def test_105_helper_url_get(self, mock_request):\n        \"\"\"successful url get with dns servers\"\"\"\n        mock_request.return_value = (\"foo\", 200, None)\n        result, status_code, error_msg = self.url_get(self.logger, \"url\", \"dns\")\n        self.assertEqual(\"foo\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n\n    @patch(\n        \"acme_srv.helpers.network.requests.get\",\n        side_effect=Mock(side_effect=Exception(\"foo\")),\n    )\n    def test_106_helper_url_get(self, mock_request):\n        \"\"\"unsuccessful url_get\"\"\"\n        # mock_request.return_value.text = 'foo'\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result, status_code, error_msg = self.url_get(self.logger, \"url\")\n            self.assertEqual(None, result)\n            self.assertEqual(500, status_code)\n            self.assertIn(\"Could not fetch URL\", error_msg)\n        self.assertIn(\"ERROR:test_a2c:foo\", lcm.output)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_107_helper_url_get(self, mock_request):\n        \"\"\"unsuccessful url_get fallback to v4\"\"\"\n        object = Mock()\n        object.text = \"foo\"\n        object.status_code = 200\n        mock_request.side_effect = [Exception(\"foo\"), object]\n        result, status_code, error_msg = self.url_get(self.logger, \"url\")\n        self.assertEqual(\"foo\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_108_helper_url_get_with_own_dns(self, mock_request):\n        \"\"\"successful url_get_with_own_dns get with dns servers\"\"\"\n        mock_request.return_value.text = \"foo\"\n        mock_request.return_value.status_code = 200\n        result, status_code, error_msg = self.url_get_with_own_dns(self.logger, \"url\")\n        self.assertEqual(\"foo\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_109_helper_url_get_with_own_dns(self, mock_request):\n        \"\"\"successful url_get_with_own_dns get with dns servers\"\"\"\n        mock_request.return_value = {\"foo\": \"foo\"}\n        result, status_code, error_msg = self.url_get_with_own_dns(self.logger, \"url\")\n        self.assertEqual(None, result)\n        self.assertEqual(500, status_code)\n        self.assertIn(\n            \"Could not get URL by using the configured DNS servers\", error_msg\n        )\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_110_helper_url_get_with_own_dns_non_200(self, mock_request):\n        \"\"\"url_get_with_own_dns with non-200 status code\"\"\"\n        mock_request.return_value.text = \"Not Found\"\n        mock_request.return_value.status_code = 404\n        mock_request.return_value.reason = \"Not Found\"\n        result, status_code, error_msg = self.url_get_with_own_dns(\n            self.logger, \"http://example.com/test\"\n        )\n        self.assertEqual(\"Not Found\", result)\n        self.assertEqual(404, status_code)\n        self.assertEqual(\"http://example.com/test Not Found\", error_msg)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_111_helper_url_get_with_own_dns_verify_false(self, mock_request):\n        \"\"\"url_get_with_own_dns with verify=False parameter\"\"\"\n        mock_request.return_value.text = \"secure content\"\n        mock_request.return_value.status_code = 200\n        result, status_code, error_msg = self.url_get_with_own_dns(\n            self.logger, \"https://secure.example.com\", verify=False\n        )\n        self.assertEqual(\"secure content\", result)\n        self.assertEqual(200, status_code)\n        self.assertEqual(None, error_msg)\n        # Verify that requests.get was called with verify=False\n        mock_request.assert_called_once()\n        call_args = mock_request.call_args\n        self.assertEqual(call_args[1][\"verify\"], False)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_112_helper_url_get_with_own_dns_connection_cleanup(self, mock_request):\n        \"\"\"url_get_with_own_dns ensures connection cleanup after exception\"\"\"\n        mock_request.side_effect = requests.exceptions.ConnectionError(\n            \"Connection failed\"\n        )\n\n        # Store original connection function\n        from acme_srv.helpers.network import connection\n\n        original_create_connection = connection.create_connection\n\n        result, status_code, error_msg = self.url_get_with_own_dns(\n            self.logger, \"http://example.com\"\n        )\n\n        # Verify exception handling\n        self.assertEqual(None, result)\n        self.assertEqual(500, status_code)\n        self.assertIn(\n            \"Could not get URL by using the configured DNS servers\", error_msg\n        )\n\n        # Verify connection was restored after exception\n        self.assertEqual(connection.create_connection, original_create_connection)\n\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_113_helper_url_get_with_own_dns_server_error(self, mock_request):\n        \"\"\"url_get_with_own_dns with server error status code\"\"\"\n        mock_request.return_value.text = \"Internal Server Error\"\n        mock_request.return_value.status_code = 500\n        mock_request.return_value.reason = \"Internal Server Error\"\n        result, status_code, error_msg = self.url_get_with_own_dns(\n            self.logger, \"http://api.example.com/endpoint\"\n        )\n        self.assertEqual(\"Internal Server Error\", result)\n        self.assertEqual(500, status_code)\n        self.assertEqual(\n            \"http://api.example.com/endpoint Internal Server Error\", error_msg\n        )\n\n    @patch(\"acme_srv.helpers.network.load_config\")\n    def test_114_helper_dns_server_list_load(self, mock_load_config):\n        \"\"\"successful dns_server_list_load with empty config file\"\"\"\n        mock_load_config.return_value = {}\n        self.assertEqual([\"9.9.9.9\", \"8.8.8.8\"], self.dns_server_list_load())\n\n    @patch(\"acme_srv.helpers.network.load_config\")\n    def test_115_helper_dns_server_list_load(self, mock_load_config):\n        \"\"\"successful dns_server_list_load with empty Challenge section\"\"\"\n        mock_load_config.return_value = {\"Challenge\": {}}\n        self.assertEqual([\"9.9.9.9\", \"8.8.8.8\"], self.dns_server_list_load())\n\n    @patch(\"acme_srv.helpers.network.load_config\")\n    def test_116_helper_dns_server_list_load(self, mock_load_config):\n        \"\"\"successful dns_server_list_load with wrong Challenge section\"\"\"\n        mock_load_config.return_value = {\"Challenge\": {\"foo\": \"bar\"}}\n        self.assertEqual([\"9.9.9.9\", \"8.8.8.8\"], self.dns_server_list_load())\n\n    @patch(\"acme_srv.helpers.network.load_config\")\n    def test_117_helper_dns_server_list_load(self, mock_load_config):\n        \"\"\"successful dns_server_list_load with wrong json format\"\"\"\n        mock_load_config.return_value = {\"Challenge\": {\"dns_server_list\": \"bar\"}}\n        self.assertEqual([\"9.9.9.9\", \"8.8.8.8\"], self.dns_server_list_load())\n\n    @patch(\"acme_srv.helpers.network.load_config\")\n    def test_118_helper_dns_server_list_load(self, mock_load_config):\n        \"\"\"successful dns_server_list_load with wrong json format\"\"\"\n        mock_load_config.return_value = {\n            \"Challenge\": {\"dns_server_list\": '[\"foo\", \"bar\"]'}\n        }\n        self.assertEqual([\"foo\", \"bar\"], self.dns_server_list_load())\n\n    def test_119_helper_csr_san_get(self):\n        \"\"\"get sans but no csr\"\"\"\n        csr = None\n        self.assertEqual([], self.csr_san_get(self.logger, csr))\n\n    def test_120_helper_csr_san_get(self):\n        \"\"\"get sans but one san with ==\"\"\"\n        csr = \"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEANAXOIkv0CovmdzyoAv1dsiK0TK2XHBdBTEPFDsrT7MnrIXOFS4FnDrg8zpn7QBzBRTl3HaKN8fnpIHkA/6ZRDqaEJq0AeskjxIg9LKDBBx5TEdgPh1CwruRWLlXtrqU7XXQmk0wLIo/kfaDRcTjyJ3yHTEK06mCAaws0sTKlTw2D4pIiDRp8zbLHeSEUX5UKOSGbLSSUY/F2XwgPB8nC2BCD/gkvHRR+dMQSdOCiS9GLwZdYAAyESw6WhmGPjmVbeTRgSt/9//yx3JKQgkFYmpSMLKR2G525M+l1qfku/4b0iMOa4vQjFRj5AXZH0SBpAKtvnFxUpP6P9mTE7+akOQ==\"\n        self.assertEqual([\"DNS:foo1.bar.local\"], self.csr_san_get(self.logger, csr))\n\n    def test_121_helper_csr_san_get(self):\n        \"\"\"get sans but one san without ==\"\"\"\n        csr = \"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEANAXOIkv0CovmdzyoAv1dsiK0TK2XHBdBTEPFDsrT7MnrIXOFS4FnDrg8zpn7QBzBRTl3HaKN8fnpIHkA/6ZRDqaEJq0AeskjxIg9LKDBBx5TEdgPh1CwruRWLlXtrqU7XXQmk0wLIo/kfaDRcTjyJ3yHTEK06mCAaws0sTKlTw2D4pIiDRp8zbLHeSEUX5UKOSGbLSSUY/F2XwgPB8nC2BCD/gkvHRR+dMQSdOCiS9GLwZdYAAyESw6WhmGPjmVbeTRgSt/9//yx3JKQgkFYmpSMLKR2G525M+l1qfku/4b0iMOa4vQjFRj5AXZH0SBpAKtvnFxUpP6P9mTE7+akOQ\"\n        self.assertEqual([\"DNS:foo1.bar.local\"], self.csr_san_get(self.logger, csr))\n\n    def test_122_helper_csr_san_get(self):\n        \"\"\"get sans but two sans\"\"\"\n        csr = \"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBADeuf4J8Xziw2OuvLNnLOSgHQl2HdMFtRdgJoun7zPobsP3L3qyXLvvhJcQsIJggu5ZepnHGrCxroSbtRSO65GtLQA0Rq3DCGcPIC1fe9AYrqoynx8bWt2Hd+PyDrBppHVoQzj6yNCt6XNSDs04BMtjs9Pu4DD6DDHmxFMVNdHXea2Rms7C5nLQvXgw7yOF3Zk1vEu7Kue7d3zZMhN+HwwrNEA7RGAEzHHlCv5LL4Mw+kf6OJ8nf/WDiLDKEQIh6bnOuB42Y2wUMpzui8Uur0VJO+twY46MvjiVMMBZE3aPJU33eNPAQVC7GinStn+zQIJA5AADdcO8Lk1qdtaDiGp8\"\n        self.assertEqual(\n            [\"DNS:foo1.bar.local\", \"DNS:foo2.bar.local\"],\n            self.csr_san_get(self.logger, csr),\n        )\n\n    def test_123_helper_csr_san_get(self):\n        \"\"\"get sans but three sans\"\"\"\n        csr = \"MIICtzCCAZ8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgWTBXBgkqhkiG9w0BCQ4xSjBIMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwgg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWyCDmZvbzMuYmFyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQAQRkub6G4uijaXOYpCkoz40I+SVRsbRDgnMNjsooZz1+7DVglFjrr6Pb0PPTOvOxtmbHP2KK0WokDn4LqOD2t0heuI+KPQy7m/ROpOB/YZOzTWEB8yS4vjkf/RFiJ7fnCAc8vA+3K/mBVb+89F8w/KlyPmpg1GK7UNgjEa5bnznTox8q12CocCJVykPEiC8AT/VPWUOPfg6gs+V6LO8R73VRPMVy0ttYKGX80ob+KczDTMUhoxXg8OG+G+bXXU+4Tu4l+nQWf2lFejECi/vNKzUT90IbcGJwyk7rc4Q7BJ/t/5nMo+vuV9f+2HI7qakHcw6u9RGylL4OYDf1CrqF1R\"\n        self.assertEqual(\n            [\"DNS:foo1.bar.local\", \"DNS:foo2.bar.local\", \"DNS:foo3.bar.local\"],\n            self.csr_san_get(self.logger, csr),\n        )\n\n    def test_124_helper_csr_san_get(self):\n        \"\"\"get sans but three sans\"\"\"\n        csr = \"MIIBFjCBvQIBADAYMRYwFAYDVQQDEw1mb28uYmFyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETOQukalTTCD8y7zoAsmxeAWlbi9oZtzh7XQc7A7KF4fZLP3pYjoZG6s+sXCp7bUpKhuIejrDRp1cFE5NlEK8jaBDMEEGCSqGSIb3DQEJDjE0MDIwMAYDVR0RBCkwJ4INZm9vLmJhci5sb2NhbIcEwKgOg4cQ/oAAAAAAAAACFV3//sABAjAKBggqhkjOPQQDAgNIADBFAiBKUb5r/8aSN4/utaDoi0vIcaASVZz8p1nSJ1YWSCkIpAIhAI20iVBu5j0tBmTc3uRzKIYTqsnXpH0UV8bcONy4m1Sa\"\n        self.assertEqual(\n            [\"DNS:foo.bar.local\", \"IP:192.168.14.131\", \"IP:fe80::215:5dff:fec0:102\"],\n            self.csr_san_get(self.logger, csr),\n        )\n\n    def test_125_helper_csr_san_byte_get(self):\n        \"\"\"get sans but two sans\"\"\"\n        csr = \"MIICpzCCAY8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgSTBHBgkqhkiG9w0BCQ4xOjA4MAsGA1UdDwQEAwIF4DApBgNVHREEIjAggg5mb28xLmJhci5sb2NhbIIOZm9vMi5iYXIubG9jYWwwDQYJKoZIhvcNAQELBQADggEBADeuf4J8Xziw2OuvLNnLOSgHQl2HdMFtRdgJoun7zPobsP3L3qyXLvvhJcQsIJggu5ZepnHGrCxroSbtRSO65GtLQA0Rq3DCGcPIC1fe9AYrqoynx8bWt2Hd+PyDrBppHVoQzj6yNCt6XNSDs04BMtjs9Pu4DD6DDHmxFMVNdHXea2Rms7C5nLQvXgw7yOF3Zk1vEu7Kue7d3zZMhN+HwwrNEA7RGAEzHHlCv5LL4Mw+kf6OJ8nf/WDiLDKEQIh6bnOuB42Y2wUMpzui8Uur0VJO+twY46MvjiVMMBZE3aPJU33eNPAQVC7GinStn+zQIJA5AADdcO8Lk1qdtaDiGp8\"\n        self.assertEqual(\n            \"MCCCDmZvbzEuYmFyLmxvY2Fsgg5mb28yLmJhci5sb2NhbA==\",\n            self.csr_san_byte_get(self.logger, csr),\n        )\n\n    @patch(\"acme_srv.helpers.csr.csr_load\")\n    def test_126_helper_csr_san_get(self, mock_csrload):\n        \"\"\"get sans but three sans\"\"\"\n        csr = \"csr\"\n        mock_csrload.return_value = \"mock_csrload\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual([], self.csr_san_get(self.logger, csr))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting SANs from CSR: 'str' object has no attribute 'extensions'\",\n            lcm.output,\n        )\n\n    def test_127_helper_csr_extensions_get(self):\n        \"\"\"get sns in hex\"\"\"\n        csr = \"MIIClzCCAX8CAQAwGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMwfxxbCCTsZY8mTFZkoQ5cAJyQZLUiz34sDDRvEpI9ZzdNNm2AEZR7AgKNuBkLwzUzY5iQ182huNzYJYZZEvYX++ocF2ngapTMQgfB+bWS5bpWIdjnAcz1/86jmJgTciwL25dSnEWL17Yn3pAWweoewr730rq/PMyIbviQrasksnSo7abe2mctxkHjHb5sZ+Z1yRTN6ir/bObXmxr+vHeeD2vLRv4Hd5XaA1d+k31J2FVMnrn5OpWbxGHo49zd0xdy2mgTdZ9UraLaQnyGlkjYzV0rqHIAIm8HOUjGN5U75/rlOPF0x62FCICZU/z1AgRvugaA5eO8zTSQJiMiBe3AgMBAAGgOTA3BgkqhkiG9w0BCQ4xKjAoMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEANAXOIkv0CovmdzyoAv1dsiK0TK2XHBdBTEPFDsrT7MnrIXOFS4FnDrg8zpn7QBzBRTl3HaKN8fnpIHkA/6ZRDqaEJq0AeskjxIg9LKDBBx5TEdgPh1CwruRWLlXtrqU7XXQmk0wLIo/kfaDRcTjyJ3yHTEK06mCAaws0sTKlTw2D4pIiDRp8zbLHeSEUX5UKOSGbLSSUY/F2XwgPB8nC2BCD/gkvHRR+dMQSdOCiS9GLwZdYAAyESw6WhmGPjmVbeTRgSt/9//yx3JKQgkFYmpSMLKR2G525M+l1qfku/4b0iMOa4vQjFRj5AXZH0SBpAKtvnFxUpP6P9mTE7+akOQ\"\n        self.assertEqual(\n            [\"AwIF4A==\", \"MBCCDmZvbzEuYmFyLmxvY2Fs\"],\n            self.csr_extensions_get(self.logger, csr),\n        )\n\n    def test_128_helper_csr_extensions_get(self):\n        \"\"\"get tnauth identifier\"\"\"\n        csr = \"MIICuzCCAaMCAQAwHjEcMBoGA1UEAwwTY2VydC5zdGlyLmJhci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsLm4zgkl2lEx2EHy1ENfh3cYB79Xb5sD3ehkY+1pXphIWoM9KYVqHKOurModjsh75YjRBSilRfTFSk6kCUahTJyeCbM6Vzl75CcZy7poUxiK+u80JMU/xymUsrqY4GZlh2/XtFMxXHUSf3bhKZAIjBNugsvR/sHtEvJ6RJiuYqHMWUzZ/Vby5L0ywNl+LPSY7AVTUAZ0lKrnUCP4dHnbjwjf+nPi7vT6G0yrEg0qPOYXtJOXdf7vvjLi8J+ap758NtG2qapLdbToIPr0uOEvMO6zs8z1bIyjOHU3kzlpKHzDsPYy8txxKC/3Rae7sKB9gWm8WUxFBmuA7gaFDGQAECAwEAAaBYMFYGCSqGSIb3DQEJDjFJMEcwCwYDVR0PBAQDAgXgMB4GA1UdEQQXMBWCE2NlcnQuc3Rpci5iYXIubG9jYWwwGAYIKwYBBQUHARoEDDAKoAgWBjEyMzQ1NjANBgkqhkiG9w0BAQsFAAOCAQEAjyhJfgb/zJBMYp6ylRtEXgtBpsX9ePUL/iLgIDMcGtwaFm3pkQOSBr4xiTxftnqN77SlC8UEu7PDR73JX6iqLNJWucPlhAXVrr367ygO8GGLrtGddClZmo0lhRBRErgpagWB/jFkbL8afPGJwgQQXF0KWFMcajAPiIl1l6M0w11KqJ23Pwrmi7VJHzIgh4ys0D2UrX7KuV4PIOOmG0s7jTfBSB+yUH2zwVzOAzbr3wrD1WubD7hRaHDUi4bn4DRbquQOzbqfTI6QhetUcNpq4DwhBRcnZwUMJUIcxLAsFnDgGSW+dmJe6JH8MsS+8ZmOLllyQxWzYEVquQQvxFVTZA\"\n        self.assertEqual(\n            [\"AwIF4A==\", \"MBWCE2NlcnQuc3Rpci5iYXIubG9jYWw=\", \"MAqgCBYGMTIzNDU2\"],\n            self.csr_extensions_get(self.logger, csr),\n        )\n\n    def test_129_helper_validate_email(self):\n        \"\"\"validate email containing \"-\" in domain\"\"\"\n        self.assertTrue(self.validate_email(self.logger, \"foo@example-example.com\"))\n\n    def test_130_helper_validate_email(self):\n        \"\"\"validate email containing \"-\" in user\"\"\"\n        self.assertTrue(self.validate_email(self.logger, \"foo-foo@example.com\"))\n\n    def test_131_helper_get_url(self):\n        \"\"\"get_url with xforwarded https\"\"\"\n        data_dic = {\n            \"HTTP_X_FORWARDED_PROTO\": \"https\",\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"443\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"https://http_host\", self.get_url(data_dic, False))\n\n    def test_132_helper_get_url(self):\n        \"\"\"get_url with xforwarded http\"\"\"\n        data_dic = {\n            \"HTTP_X_FORWARDED_PROTO\": \"http\",\n            \"HTTP_HOST\": \"http_host\",\n            \"SERVER_PORT\": \"443\",\n            \"PATH_INFO\": \"path_info\",\n        }\n        self.assertEqual(\"http://http_host\", self.get_url(data_dic, False))\n\n    def test_133_helper_validate_email(self):\n        \"\"\"validate email containing first letter of domain cannot be a number\"\"\"\n        self.assertFalse(self.validate_email(self.logger, \"foo@1example.com\"))\n\n    def test_134_helper_validate_email(self):\n        \"\"\"validate email containing last letter of domain cannot -\"\"\"\n        self.assertFalse(self.validate_email(self.logger, \"foo@example-.com\"))\n\n    def test_135_helper_cert_dates_get(self):\n        \"\"\"get issuing and expiration date from rsa certificate\"\"\"\n        cert = \"MIIElTCCAn2gAwIBAgIRAKD_ulfqPUn-ggOUHOxjp40wDQYJKoZIhvcNAQELBQAwSDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEXMBUGA1UECgwOQWNtZTJDZXJ0aWZpZXIxDzANBgNVBAMMBnN1Yi1jYTAeFw0yMDA1MjcxMjMwMjNaFw0yMDA2MjYxMjMwMjNaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbx-z-9wsEewBf1hnk3yAy5TFg-lWVdwk2QRdAMDTExVP823QF_K-t6cxJV_-QuWVbHN-lx6nQCXIqCZSN97hN0YTkrw8jnA4FpZzyvYI9rKEO3p4sxqndbu4X-gtyMBbXOLhjTlN2f7Z081XWIgkikvuZU2XzMZ-BbRFDfsPdDRwbwvgJU6NxpdIKm2DmYIP1MFo-tLu0toAc0nm9v8Otme28_kpJxmW3iOMkqN9BE-qAkggFDeNoxPtXRyP2PrRgbaj94e1uznsyni7CYw_a9O1NPrjKFQmPaCk8k9ICvPoLHXtLabekCmvdxyRlDwD_Xoaygpd9-UHCREhcOu_wIDAQABo4GoMIGlMAsGA1UdDwQEAwIF4DAZBgNVHREEEjAQgg5mb28xLmJhci5sb2NhbDAdBgNVHQ4EFgQUqy5KOBlkyX29l4EHTCSzhZuDg-EwDgYDVR0PAQH_BAQDAgWgMB8GA1UdIwQYMBaAFBs0P896R0FUZHfnxMJL52ftKQOkMAwGA1UdEwEB_wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQB7pQpILzxqcU2RKlr17rcne6NSJTUJnNXALeUFy5PrnjjJY1_B1cKaWluk3p7AMFvUjBpcucGCfEDudW290AQxYjrvl8_ePkzRzEkAo76L7ZqED5upYBZVn_3lA5Alr8L67UC0bDMhKTsy8WJzhWHQlMb37_YFUvtNPoI_MI09Q842VXeNQz5UDZmW9qhyeDIkf6fwOAO66VnGTLuUm2LGQZ-St2GauxR0ZUcRtMJoc-c7WOdHs8DlUCoFtglrzVH98501Sx749CG4nkJr4QNDpkw2hAhlo4Cxzp6PlljPNSgM9MsqqVdrgqDteDM_n-yrVFGezCik4QexDkWARPutRLQtpbhudExVnoFM68ihZ0y3oeDjgUBLybBQpcBAsBqiJ66Q8HTZRSqO9zlKW5Vm1KwAVDh_qgELxvqd0wIVkyxBKPta2l1fvb5YBiVqo4JyNcCTnoBS1emO4vk8XjroKijwLnU0cEXwHrY4JF1uU_kOtoZMGPul5EuBMcODLs7JJ3_IqJd8quI7Vf5zSsaB6nSzQ8XmiQiVogKflBeLl7AWmYCiL-FLP_q4dSJmvdr6fPMNy4-cfDO4Awc8RNfv-VjF5Mq57X1IXJrWKkat4lCEoPMq5WRJV8uVm6XNdwvUJxgCYR9mfol7T6imODDd7BNV4dKYvyteoS0auC0iww\"\n        self.assertEqual(\n            (1590582623, 1593174623), self.cert_dates_get(self.logger, cert)\n        )\n\n    def test_136_helper_cert_dates_get(self):\n        \"\"\"get issuing and expiration date no certificate\"\"\"\n        cert = None\n        self.assertEqual((0, 0), self.cert_dates_get(self.logger, cert))\n\n    def test_137_helper_cert_dates_get(self):\n        \"\"\"get issuing and expiration date damaged certificate\"\"\"\n        cert = \"foo\"\n        self.assertEqual((0, 0), self.cert_dates_get(self.logger, cert))\n\n    def test_138_helper_cert_dates_get(self):\n        \"\"\"get issuing and expiration date ecc certificate\"\"\"\n        cert = \"MIIDozCCAYugAwIBAgIIMMxkE7mRR+YwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMDA3MTEwNDUzMTFaFw0yMTA3MTEwNDUzMTFaMBkxFzAVBgNVBAMMDmZvbzEuYmFyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER/KMoV5+zQgegqYue2ztPK2nZVpK2vxb02UzwyHw4ebhJ2gBobI23lSBRa1so1ug0kej7U+ohm5aGFdNxLM0G6OBqDCBpTALBgNVHQ8EBAMCBeAwGQYDVR0RBBIwEIIOZm9vMS5iYXIubG9jYWwwHQYDVR0OBBYEFCSaU743wU8jMETIO381r13tVLdMMA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBS/3o6OBiIiq61DyN3UT6irSEE+1TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAmmhHuBhXNM2Azv53rCKY72yTQIoDVHjYrAvTmS6NsJzYflEOMkI7FCes64dWp54BerSD736Yax67b4XmLXc/+T41d7QAcnhY5xvLJiMpSsW37icHcLZpjlOrYDoRmny2U7n6t1aQ03nwgV+BgdaUQYLkUZuczs4kdqH1c9Ot9CCRTHpqSWlmWzGeRgt2uT4gKhFESP9lzx37YwKBHulBGthv1kcAaz8w8iPXBg01OEDiraXCBZFoYDEpDi2w2Y6ChCr7sNsY7aJ3a+2iHGYlktXEntk78S+g00HW61G9oLoRgeqEH3L6qVIpnswPAU/joub0YhNBIUFenCj8c3HMBgMcczzdZL+qStdymhpVkZetzXtMTKtgmxhkRzAOQUBBcHFc+wM97FqC0S4HJAuoHQ4EJ46MxwZH0jBVqcqCPMSaJ88uV902+VGGXrnxMR8RbGWLoCmsYb1ISmBUt+31PjMCYbXKwLmzvbRpO7XAQimvtOqoufl5yeRUJRLcUS6Let0QzU196/nZ789d7Etep7RjDYQm7/QhiWH197yKZ5/mUxqfyHDQ3hk5iX7S/gbo1jQXElEv5tB8Ozs+zVQmB2bXpN8c+8XUaZnwvYC2y+0LAQN4z7xilReCaasxQSsEOLCrlsannkGV704HYnnaKBS2tI948QotHnADHdfHl3o\"\n        self.assertEqual(\n            (1594443191, 1625979191), self.cert_dates_get(self.logger, cert)\n        )\n\n    @patch(\"acme_srv.helpers.certificates.date_to_uts_utc\")\n    @patch(\"acme_srv.helpers.certificates.cert_load\")\n    def test_139_helper_cert_dates_get(self, mock_cert, mock_dates):\n        \"\"\"get issuing and expiration date excaption\"\"\"\n        mock_dates.side_effect = [Exception(\"not_valid_before_utc\"), 123, 456]\n        mock_cert = Mock()\n        mock_cert.not_valid_before_utc.side_effect = Exception(\"not_valid_before_utc\")\n        mock_cert.not_valid_after_utc.side_effect = Exception(\"not_valid_after_utc\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual((123, 456), self.cert_dates_get(self.logger, \"cert\"))\n        self.assertIn(\n            \"DEBUG:test_a2c:Error while getting dates from certificate. Fallback to deprecated method: not_valid_before_utc\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.certificates.date_to_uts_utc\")\n    @patch(\"acme_srv.helpers.certificates.cert_load\")\n    def test_140_helper_cert_dates_get(self, mock_cert, mock_dates):\n        \"\"\"get issuing and expiration date excaption\"\"\"\n        mock_dates.side_effect = [Exception(\"uts\")]\n        mock_cert = Mock()\n        mock_cert.not_valid_before_utc.side_effect = Exception(\"not_valid_before_utc\")\n        mock_cert.not_valid_after_utc.side_effect = Exception(\"not_valid_after_utc\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual((0, 0), self.cert_dates_get(self.logger, \"cert\"))\n        self.assertIn(\n            \"DEBUG:test_a2c:Error while getting dates from certificate. Fallback to deprecated method: uts\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting dates from certificate: uts\", lcm.output\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_141_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering github\"\"\"\n        mock_resolve.return_value.query.return_value = [\"foo\"]\n        self.assertEqual(\n            (None, False, None),\n            self.fqdn_resolve(self.logger, \"foo\", dnssrv=\"10.0.0.1\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_142_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering github\"\"\"\n        mock_resolve.return_value.resolve.return_value = [\"foo\"]\n        self.assertEqual(\n            (\"foo\", False, None),\n            self.fqdn_resolve(self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_143_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning a single entry and catch_single\"\"\"\n        mock_resolve.return_value.resolve.return_value = [\"foo\"]\n        self.assertEqual((None, False, None), self.fqdn_resolve(self.logger, \"foo\"))\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_144_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning two entries but catch singles\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [[\"v41\", \"v42\"], [\"v61\", \"v62\"]]\n        self.assertEqual(\n            (\"v41\", False, None),\n            self.fqdn_resolve(self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_145_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning only ipv6 and catchsingle\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [[], [\"v61\", \"v62\"]]\n        self.assertEqual(\n            (\"v61\", False, None),\n            self.fqdn_resolve(self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_146_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning list and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [[\"v41\", \"v42\"], [\"v61\", \"v62\"]]\n        self.assertEqual(\n            ([\"v41\", \"v42\", \"v61\", \"v62\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\", catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_147_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering list but no v4 and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [[], [\"v61\", \"v62\"]]\n        self.assertEqual(\n            ([\"v61\", \"v62\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\", catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_148_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning list v6 only and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [[\"v41\", \"v42\"], []]\n        self.assertEqual(\n            ([\"v41\", \"v42\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\", catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_149_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering list but no v4 and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [\n            Exception(\"foo\"),\n            [\"v61\", \"v62\"],\n        ]\n        self.assertEqual(\n            ([\"v61\", \"v62\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\", catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_150_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning list v6 only and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = [\n            [\"v41\", \"v42\"],\n            Exception(\"foo\"),\n        ]\n        self.assertEqual(\n            ([\"v41\", \"v42\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=\"10.0.0.1\", catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_151_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering list but no v4 and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=[dns.resolver.NXDOMAIN, [\"v61\", \"v62\"]]\n        )\n        self.assertEqual(\n            ([\"v61\", \"v62\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=[\"10.0.0.1\"], catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_152_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning list v6 only and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=[[\"v41\", \"v42\"], dns.resolver.NXDOMAIN]\n        )\n        self.assertEqual(\n            ([\"v41\", \"v42\"], False, None),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=[\"10.0.0.1\"], catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_153_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning covering list but no v4 and catch_all\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NXDOMAIN\n        )\n        err_msg = \"A: NXDOMAIN: foo.bar.local does not exist; AAAA: NXDOMAIN: foo.bar.local does not exist\"\n        self.assertEqual(\n            ([], True, err_msg),\n            self.fqdn_resolve(\n                self.logger, \"foo.bar.local\", dnssrv=[\"10.0.0.1\"], catch_all=True\n            ),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_154_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning one value\"\"\"\n        mock_resolve.return_value.resolve.return_value = [\"foo\"]\n        self.assertEqual(\n            (\"foo\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_155_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"successful dns-query returning two values\"\"\"\n        mock_resolve.return_value.resolve.return_value = [\"bar\", \"foo\"]\n        self.assertEqual(\n            (\"bar\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_156_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch NXDOMAIN\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NXDOMAIN\n        )\n        self.assertEqual(\n            (\n                None,\n                True,\n                \"A: NXDOMAIN: foo.bar.local does not exist; AAAA: NXDOMAIN: foo.bar.local does not exist\",\n            ),\n            self.fqdn_resolve(self.logger, \"foo.bar.local\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_157_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch NoAnswer\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NoAnswer\n        )\n        err_msg = \"A: No A record found for foo.bar.local; AAAA: No AAAA record found for foo.bar.local\"\n        self.assertEqual(\n            (None, True, err_msg), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_158_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch other dns related execption\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NoNameservers\n        )\n        err_msg = \"A: DNS resolution error: All nameservers failed to answer the query.; AAAA: DNS resolution error: All nameservers failed to answer the query.\"\n        self.assertEqual(\n            (None, True, err_msg), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_159_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch other execption\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=Exception(\"foo\")\n        )\n        self.assertEqual(\n            (\n                None,\n                True,\n                \"A: DNS resolution error: foo; AAAA: DNS resolution error: foo\",\n            ),\n            self.fqdn_resolve(self.logger, \"foo.bar.local\"),\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_160_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch NXDOMAIN on v4 and fine in v6\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NXDOMAIN\n        ), [\"foo\"]\n        self.assertEqual(\n            (\"foo\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_161_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch NoAnswer on v4 and fine in v6\"\"\"\n        mock_resolve.return_value.resolve.side_effect = Mock(\n            side_effect=dns.resolver.NoAnswer\n        ), [\"foo\"]\n        self.assertEqual(\n            (\"foo\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_162_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch other dns related execption on v4 and fine in v6\"\"\"\n        mock_resolve.return_value.resolve.side_effect = ([Exception(\"foo\"), [\"foo\"]],)\n        self.assertEqual(\n            (\"foo\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    @patch(\"dns.resolver.Resolver\")\n    def test_163_helper_fqdn_resolve(self, mock_resolve):\n        \"\"\"catch other execption when resolving v4 but fine in v6\"\"\"\n        mock_resolve.return_value.resolve.side_effect = ([Exception(\"foo\"), [\"foo\"]],)\n        self.assertEqual(\n            (\"foo\", False, None), self.fqdn_resolve(self.logger, \"foo.bar.local\")\n        )\n\n    def test_164_helper_signature_check(self):\n        \"\"\"sucessful validation symmetric key\"\"\"\n        mkey = '{\"k\": \"ZndUSkZvVldvMEFiRzQ5VWNCdERtNkNBNnBTcTl4czNKVEVxdUZiaEdpZXZNUVJBVmRuSFREcDJYX2s3X0NxTA\", \"kty\": \"oct\"}'\n        message = '{\"payload\": \"eyJlIjogIkFRQUIiLCAia3R5IjogIlJTQSIsICJuIjogIm5kN3ZWUTNraW4zS3BKdTd6RUZNTlVPb0ZIQmVDUWRFRTUyOF9iOHo2djNDNnYtQS0zeUdBcTFWTjZmRTluUXdYSmNlZ2ZNdm1MczlCVVllVjZ2M1FzdGhkVFRCdW5FS1l0TVVZUVRmNkpwaHNEb1pHTkt1dnpCY2ZxSlN2TXpCNHdwa3hORm1Pa2M1QVhwRzhnQWJiTTRuS3JDQkdCQ21lZ2RJUEc3U0g3Mk9tejN6YjIwemZfZlo4dHVoUzk1eUJKdndKRjhZRGtCdDViWUV5ZnQ4aVoyWVFGVmRZZW5FMDhKOGRBUGNVQy1HYld6NmJXUm9Xc0xOT21VNkVjSndsSV9tRXRqazA5aTNlVEhOa2Vna3NrZUJOeXhlSkdtaVRtMHRtS1MwOEVvY0VQTDA1UktxSm9XNnhVcHNITDcwSzdzUVRaUDBHSUY1VXBwSkZXMnlVdyJ9\", \"protected\": \"eyJ1cmwiOiAiaHR0cDovL2FjbWUtc3J2LmJhci5sb2NhbC9hY21lL25ld2FjY291bnQiLCAiYWxnIjogIkhTMjU2IiwgImtpZCI6ICJiYXIifQ\", \"signature\": \"VXYLfPuoClsn_rhPPV8qjspZV1Q7HyX8rXv6odWYnLI\"}'\n        self.assertEqual(\n            (True, None), self.signature_check(self.logger, message, mkey, json_=True)\n        )\n\n    def test_165_helper_signature_check(self):\n        \"\"\"sucessful validation wrong symmetric key\"\"\"\n        mkey = '{\"k\": \"ZndUSkZvVldvMEFiRzQ5VWNCdERtNkNBNnBTcTl4czNKVEVxdUZiaEdpZXZNUVJBVmRuSFREcDJYX2s3X0NxvA\", \"kty\": \"oct\"}'\n        message = '{\"payload\": \"eyJlIjogIkFRQUIiLCAia3R5IjogIlJTQSIsICJuIjogIm5kN3ZWUTNraW4zS3BKdTd6RUZNTlVPb0ZIQmVDUWRFRTUyOF9iOHo2djNDNnYtQS0zeUdBcTFWTjZmRTluUXdYSmNlZ2ZNdm1MczlCVVllVjZ2M1FzdGhkVFRCdW5FS1l0TVVZUVRmNkpwaHNEb1pHTkt1dnpCY2ZxSlN2TXpCNHdwa3hORm1Pa2M1QVhwRzhnQWJiTTRuS3JDQkdCQ21lZ2RJUEc3U0g3Mk9tejN6YjIwemZfZlo4dHVoUzk1eUJKdndKRjhZRGtCdDViWUV5ZnQ4aVoyWVFGVmRZZW5FMDhKOGRBUGNVQy1HYld6NmJXUm9Xc0xOT21VNkVjSndsSV9tRXRqazA5aTNlVEhOa2Vna3NrZUJOeXhlSkdtaVRtMHRtS1MwOEVvY0VQTDA1UktxSm9XNnhVcHNITDcwSzdzUVRaUDBHSUY1VXBwSkZXMnlVdyJ9\", \"protected\": \"eyJ1cmwiOiAiaHR0cDovL2FjbWUtc3J2LmJhci5sb2NhbC9hY21lL25ld2FjY291bnQiLCAiYWxnIjogIkhTMjU2IiwgImtpZCI6ICJiYXIifQ\", \"signature\": \"VXYLfPuoClsn_rhPPV8qjspZV1Q7HyX8rXv6odWYnLI\"}'\n\n        if int(\"%i%i\" % (sys.version_info[0], sys.version_info[1])) <= 36:\n            error = \"Verification failed for all signatures[\\\"Failed: [InvalidJWSSignature('Verification failed',)]\\\"]\"\n        else:\n            error = \"Verification failed for all signatures[\\\"Failed: [InvalidJWSSignature('Verification failed')]\\\"]\"\n        self.assertEqual(\n            (False, error), self.signature_check(self.logger, message, mkey, json_=True)\n        )\n\n    def test_166_helper_signature_check(self):\n        \"\"\"sucessful validation wrong symmetric key without json_ flag set\"\"\"\n        mkey = '{\"k\": \"ZndUSkZvVldvMEFiRzQ5VWNCdERtNkNBNnBTcTl4czNKVEVxdUZiaEdpZXZNUVJBVmRuSFREcDJYX2s3X0NxvA\", \"kty\": \"oct\"}'\n        message = '{\"payload\": \"eyJlIjogIkFRQUIiLCAia3R5IjogIlJTQSIsICJuIjogIm5kN3ZWUTNraW4zS3BKdTd6RUZNTlVPb0ZIQmVDUWRFRTUyOF9iOHo2djNDNnYtQS0zeUdBcTFWTjZmRTluUXdYSmNlZ2ZNdm1MczlCVVllVjZ2M1FzdGhkVFRCdW5FS1l0TVVZUVRmNkpwaHNEb1pHTkt1dnpCY2ZxSlN2TXpCNHdwa3hORm1Pa2M1QVhwRzhnQWJiTTRuS3JDQkdCQ21lZ2RJUEc3U0g3Mk9tejN6YjIwemZfZlo4dHVoUzk1eUJKdndKRjhZRGtCdDViWUV5ZnQ4aVoyWVFGVmRZZW5FMDhKOGRBUGNVQy1HYld6NmJXUm9Xc0xOT21VNkVjSndsSV9tRXRqazA5aTNlVEhOa2Vna3NrZUJOeXhlSkdtaVRtMHRtS1MwOEVvY0VQTDA1UktxSm9XNnhVcHNITDcwSzdzUVRaUDBHSUY1VXBwSkZXMnlVdyJ9\", \"protected\": \"eyJ1cmwiOiAiaHR0cDovL2FjbWUtc3J2LmJhci5sb2NhbC9hY21lL25ld2FjY291bnQiLCAiYWxnIjogIkhTMjU2IiwgImtpZCI6ICJiYXIifQ\", \"signature\": \"VXYLfPuoClsn_rhPPV8qjspZV1Q7HyX8rXv6odWYnLI\"}'\n        if int(\"%i%i\" % (sys.version_info[0], sys.version_info[1])) < 39:\n            error = \"type object argument after ** must be a mapping, not str\"\n        else:\n            error = \"jwcrypto.jwk.JWK() argument after ** must be a mapping, not str\"\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (False, error), self.signature_check(self.logger, message, mkey)\n            )\n        self.assertIn(\"ERROR:test_a2c:No jwkey extracted\", lcm.output)\n\n    def test_167_helper_signature_check(self):\n        \"\"\"sucessful validation invalid key\"\"\"\n        mkey = \"invalid key\"\n        message = '{\"payload\": \"eyJlIjogIkFRQUIiLCAia3R5IjogIlJTQSIsICJuIjogIm5kN3ZWUTNraW4zS3BKdTd6RUZNTlVPb0ZIQmVDUWRFRTUyOF9iOHo2djNDNnYtQS0zeUdBcTFWTjZmRTluUXdYSmNlZ2ZNdm1MczlCVVllVjZ2M1FzdGhkVFRCdW5FS1l0TVVZUVRmNkpwaHNEb1pHTkt1dnpCY2ZxSlN2TXpCNHdwa3hORm1Pa2M1QVhwRzhnQWJiTTRuS3JDQkdCQ21lZ2RJUEc3U0g3Mk9tejN6YjIwemZfZlo4dHVoUzk1eUJKdndKRjhZRGtCdDViWUV5ZnQ4aVoyWVFGVmRZZW5FMDhKOGRBUGNVQy1HYld6NmJXUm9Xc0xOT21VNkVjSndsSV9tRXRqazA5aTNlVEhOa2Vna3NrZUJOeXhlSkdtaVRtMHRtS1MwOEVvY0VQTDA1UktxSm9XNnhVcHNITDcwSzdzUVRaUDBHSUY1VXBwSkZXMnlVdyJ9\", \"protected\": \"eyJ1cmwiOiAiaHR0cDovL2FjbWUtc3J2LmJhci5sb2NhbC9hY21lL25ld2FjY291bnQiLCAiYWxnIjogIkhTMjU2IiwgImtpZCI6ICJiYXIifQ\", \"signature\": \"VXYLfPuoClsn_rhPPV8qjspZV1Q7HyX8rXv6odWYnLI\"}'\n        self.assertEqual(\n            (False, \"\"), self.signature_check(self.logger, message, mkey, json_=True)\n        )\n\n    def test_168_fqdn_in_san_check(self):\n        \"\"\"successful check one entry one match\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = [\"DNS:foo.bar.local\"]\n        self.assertTrue(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_169_fqdn_in_san_check(self):\n        \"\"\"successful check two entries one match\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = [\"DNS:foo1.bar.local\", \"DNS:foo.bar.local\"]\n        self.assertTrue(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_170_fqdn_in_san_check(self):\n        \"\"\"successful check two entries no DNS one match\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = [\"IP: 10.0.0.l\", \"DNS:foo.bar.local\"]\n        self.assertTrue(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_171_fqdn_in_san_check(self):\n        \"\"\"successful check no fqdn\"\"\"\n        fqdn = None\n        san_list = [\"IP: 10.0.0.l\", \"DNS:foo.bar.local\"]\n        self.assertFalse(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_172_fqdn_in_san_check(self):\n        \"\"\"successful check no fqdn\"\"\"\n        fqdn = \"\"\n        san_list = [\"IP: 10.0.0.l\", \"DNS:foo.bar.local\"]\n        self.assertFalse(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_173_fqdn_in_san_check(self):\n        \"\"\"successful check blank fqdn\"\"\"\n        fqdn = \" \"\n        san_list = [\"IP: 10.0.0.l\", \"DNS:foo.bar.local\"]\n        self.assertFalse(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_174_fqdn_in_san_check(self):\n        \"\"\"successful check empty san_list\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = []\n        self.assertFalse(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_175_fqdn_in_san_check(self):\n        \"\"\"successful check two entries one match\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = [\"foo1.bar.local\", \"DNS:foo.bar.local\"]\n        self.assertTrue(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n\n    def test_176_fqdn_in_san_check(self):\n        \"\"\"successful check two entries one match\"\"\"\n        fqdn = \"foo.bar.local\"\n        san_list = [\"foo1.bar.local\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.fqdn_in_san_check(self.logger, san_list, fqdn))\n        self.assertIn(\n            \"ERROR:test_a2c:Error during SAN check. SAN split failed: foo1.bar.local\",\n            lcm.output,\n        )\n\n    def test_177_sha256_hash_hex(self):\n        \"\"\"sha256 digest as hex file\"\"\"\n        self.assertEqual(\n            \"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae\",\n            self.sha256_hash_hex(self.logger, \"foo\"),\n        )\n\n    def test_178_sha256_hash_hex(self):\n        \"\"\"sha256 digest as hex file\"\"\"\n        self.assertEqual(\n            \"fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9\",\n            self.sha256_hash_hex(self.logger, \"bar\"),\n        )\n\n    def test_179_sha256_hash(self):\n        \"\"\"sha256 digest\"\"\"\n        self.assertEqual(\n            b\"LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564\",\n            self.b64_url_encode(self.logger, self.sha256_hash(self.logger, \"foo\")),\n        )\n\n    def test_180_sha256_hash(self):\n        \"\"\"sha256 digest\"\"\"\n        self.assertEqual(\n            b\"_N4rLtula_QIYB-3If6bXDONEO5CnqBPrlURto-_j7k\",\n            self.b64_url_encode(self.logger, self.sha256_hash(self.logger, \"bar\")),\n        )\n\n    def test_181_b64_encode(self):\n        \"\"\"base64 encode string\"\"\"\n        self.assertEqual(\"Zm9v\", self.b64_encode(self.logger, b\"foo\"))\n\n    def test_182_b64_encode(self):\n        \"\"\"base64 encode string\"\"\"\n        self.assertEqual(\"YmFyMQ==\", self.b64_encode(self.logger, b\"bar1\"))\n\n    def test_183_b64_encode(self):\n        \"\"\"base64 encode string\"\"\"\n        self.assertEqual(\"YmFyMTI=\", self.b64_encode(self.logger, b\"bar12\"))\n\n    def test_184_cert_der2pem(self):\n        \"\"\"test cert_der2pem\"\"\"\n        b64 = \"MIIETjCCAjagAwIBAgIRAIG11e4S8ErJuwCYAKsoU3UwDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGc3ViLWNhMB4XDTIxMDYxMjA2MjMzOFoXDTIzMDYwMjA2MjMzOFowGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0lk4lyEIa0VL/u5ic01Zo/o+gyYqFpU7xe+nbFgiKA+R1rqrzP/sR6xjHqS0Rkv/BcBXf81sp/+iDmwIQLVlBTkKdimqVHCJMAbTL8ZNpcLDaRUce4liyX1cmczPTSqI/kcyEr8tKpYN+KzvKZZsNx2Pbgu7y7/70P2uSywiW+sqYZ+X28KGFxq6wwENzJtweDVsbWql9LLtw6daF41UQg10auNlRL1nhW0SlWZh1zPPW/0sa6C3xX28jjVh843b4ekkRNLXSEYQMTi0qYR2LomQ5aTlQ/hellf17UknfN2aA2RH5D7Ek+mndj/rH21bxQg26KRmHlaJld9K1IfvJAgMBAAGjgZgwgZUwCwYDVR0PBAQDAgXgMBkGA1UdEQQSMBCCDmZvbzEuYmFyLmxvY2FsMB0GA1UdDgQWBBReDKlEWwro02ljWMCi10HMqhDmbzAfBgNVHSMEGDAWgBSDJ855iatD1k7LCUzmM5yhe4IzeDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAhv7Jco6VjT25FuyOz/C0N5+q2M8sqjcDDYMwUTKXVkIc7/lSsubL8z64eS4I5iBecNOlPXASMoMe0KbdrvzqItYgeisC08rnWQayuDr/dj2Y/v4WptZdTPc0pWZQ7LUSxcZaydMFsIKxtfO2HR84DqrUbpvDfVSP7/UiN2O0TbSBiEC6Xayu6IudGZ9naHTAXzTau6SejcbH+0jWZsDXd1SbDPd3a+ZcHbDLIZAzsjcurleDPS54PIXjblOgMrsheDq/wzxKtvLOZEe8Gr6THwtX6uS0oQ72BFNGfZVVPFiL/q0Dvj2FveBtv7k14QcBqHutE4pEpYb/kcU7cxCVgGlUw8Q8trYQhBB37X9dOHjC2G8cyCeyVr+xfUE12wTKZDRIXjG3FMpKgeB4oNYPWA5m/1GBOGddhmogIB8GXeenDcAjBdVOFuuOrMInHLnLD9w7iEiopfx+six3Nxpo3thDV4xdiTZsWp9ojZhQzW8haEQleJ3Xyl65UuZKHyrRJ0OWR4LRkNwJitG5F0MYg8bjgik/cHTwzIB0HXgnaVeMBJY3sOkvCpAlTGZe1GL9foWIeFkprPG4cePrjtC3Mn8rHH0pIi1mdkcAIdexYdg/qlroKk2ROLXnX5LHmrM1CDZQphgyzLETdwXQdTBOJvc8FsDPhp5p+iqgT2e16QI=\"\n        result = b\"-----BEGIN CERTIFICATE-----\\nMIIETjCCAjagAwIBAgIRAIG11e4S8ErJuwCYAKsoU3UwDQYJKoZIhvcNAQELBQAw\\nETEPMA0GA1UEAxMGc3ViLWNhMB4XDTIxMDYxMjA2MjMzOFoXDTIzMDYwMjA2MjMz\\nOFowGTEXMBUGA1UEAwwOZm9vMS5iYXIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUA\\nA4IBDwAwggEKAoIBAQC0lk4lyEIa0VL/u5ic01Zo/o+gyYqFpU7xe+nbFgiKA+R1\\nrqrzP/sR6xjHqS0Rkv/BcBXf81sp/+iDmwIQLVlBTkKdimqVHCJMAbTL8ZNpcLDa\\nRUce4liyX1cmczPTSqI/kcyEr8tKpYN+KzvKZZsNx2Pbgu7y7/70P2uSywiW+sqY\\nZ+X28KGFxq6wwENzJtweDVsbWql9LLtw6daF41UQg10auNlRL1nhW0SlWZh1zPPW\\n/0sa6C3xX28jjVh843b4ekkRNLXSEYQMTi0qYR2LomQ5aTlQ/hellf17UknfN2aA\\n2RH5D7Ek+mndj/rH21bxQg26KRmHlaJld9K1IfvJAgMBAAGjgZgwgZUwCwYDVR0P\\nBAQDAgXgMBkGA1UdEQQSMBCCDmZvbzEuYmFyLmxvY2FsMB0GA1UdDgQWBBReDKlE\\nWwro02ljWMCi10HMqhDmbzAfBgNVHSMEGDAWgBSDJ855iatD1k7LCUzmM5yhe4Iz\\neDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAN\\nBgkqhkiG9w0BAQsFAAOCAgEAhv7Jco6VjT25FuyOz/C0N5+q2M8sqjcDDYMwUTKX\\nVkIc7/lSsubL8z64eS4I5iBecNOlPXASMoMe0KbdrvzqItYgeisC08rnWQayuDr/\\ndj2Y/v4WptZdTPc0pWZQ7LUSxcZaydMFsIKxtfO2HR84DqrUbpvDfVSP7/UiN2O0\\nTbSBiEC6Xayu6IudGZ9naHTAXzTau6SejcbH+0jWZsDXd1SbDPd3a+ZcHbDLIZAz\\nsjcurleDPS54PIXjblOgMrsheDq/wzxKtvLOZEe8Gr6THwtX6uS0oQ72BFNGfZVV\\nPFiL/q0Dvj2FveBtv7k14QcBqHutE4pEpYb/kcU7cxCVgGlUw8Q8trYQhBB37X9d\\nOHjC2G8cyCeyVr+xfUE12wTKZDRIXjG3FMpKgeB4oNYPWA5m/1GBOGddhmogIB8G\\nXeenDcAjBdVOFuuOrMInHLnLD9w7iEiopfx+six3Nxpo3thDV4xdiTZsWp9ojZhQ\\nzW8haEQleJ3Xyl65UuZKHyrRJ0OWR4LRkNwJitG5F0MYg8bjgik/cHTwzIB0HXgn\\naVeMBJY3sOkvCpAlTGZe1GL9foWIeFkprPG4cePrjtC3Mn8rHH0pIi1mdkcAIdex\\nYdg/qlroKk2ROLXnX5LHmrM1CDZQphgyzLETdwXQdTBOJvc8FsDPhp5p+iqgT2e1\\n6QI=\\n-----END CERTIFICATE-----\\n\"\n        der = self.b64_decode(self.logger, b64)\n        self.assertEqual(result, self.cert_der2pem(der))\n\n    def test_185_cert_pem2der(self):\n        \"\"\"test cert_der2pem\"\"\"\n        cert = \"\"\"-----BEGIN CERTIFICATE-----\nMIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUy\nMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrd\nFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytm\nVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/f\nZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDe\nNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfyt\nhBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMB\nAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW\n2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQR\nMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIB\nDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eA\nXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3ak\nAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7\nWzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1Ixrd\nBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6\nlEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKW\nJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1\nkqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWD\nSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ\n2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2\nCUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0\nklGUNHG98CtsmlhrivhSTJWqSIOfyKGF\n-----END CERTIFICATE-----\"\"\"\n        result = \"MIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUyMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrdFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytmVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/fZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDeNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfythBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMBAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQRMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eAXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3akAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7WzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1IxrdBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6lEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKWJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1kqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWDSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2CUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0klGUNHG98CtsmlhrivhSTJWqSIOfyKGF\"\n        self.assertEqual(result, self.b64_encode(self.logger, self.cert_pem2der(cert)))\n\n    @patch(\"acme_srv.helpers.certificates.cert_extensions_py_openssl_get\")\n    @patch(\"acme_srv.helpers.certificates.cryptography_version_get\")\n    def test_186_helper_cert_extensions_get(self, mock_version, mock_py):\n        \"\"\"test cert_san_get for a single SAN and recode = False\"\"\"\n        cert = \"\"\"-----BEGIN CERTIFICATE-----\nMIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUy\nMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrd\nFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytm\nVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/f\nZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDe\nNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfyt\nhBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMB\nAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW\n2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQR\nMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIB\nDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eA\nXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3ak\nAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7\nWzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1Ixrd\nBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6\nlEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKW\nJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1\nkqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWD\nSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ\n2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2\nCUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0\nklGUNHG98CtsmlhrivhSTJWqSIOfyKGF\n-----END CERTIFICATE-----\"\"\"\n        mock_version.return_value = 36\n        self.assertEqual(\n            [\n                \"MAA=\",\n                \"BBQ3dnSIu/UUTXsMIMF+nFbY/7n6Qg==\",\n                \"AwIDuA==\",\n                \"MAoGCCsGAQUFBwMC\",\n                \"MA+CDWVzdGNsaWVudC5lc3Q=\",\n                \"AwIFoA==\",\n                \"Fg94Y2EgY2VydGlmaWNhdGU=\",\n            ],\n            self.cert_extensions_get(self.logger, cert, recode=False),\n        )\n        self.assertFalse(mock_py.called)\n\n    @patch(\"acme_srv.helpers.certificates.cert_extensions_py_openssl_get\")\n    @patch(\"acme_srv.helpers.certificates.cryptography_version_get\")\n    def test_187_helper_cert_extensions_get(self, mock_version, mock_py):\n        \"\"\"test cert_san_get for a single SAN and recode = True\"\"\"\n        cert = \"MIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUyMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrdFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytmVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/fZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDeNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfythBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMBAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQRMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eAXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3akAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7WzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1IxrdBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6lEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKWJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1kqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWDSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2CUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0klGUNHG98CtsmlhrivhSTJWqSIOfyKGF\"\n        mock_version.return_value = 36\n        self.assertEqual(\n            [\n                \"MAA=\",\n                \"BBQ3dnSIu/UUTXsMIMF+nFbY/7n6Qg==\",\n                \"AwIDuA==\",\n                \"MAoGCCsGAQUFBwMC\",\n                \"MA+CDWVzdGNsaWVudC5lc3Q=\",\n                \"AwIFoA==\",\n                \"Fg94Y2EgY2VydGlmaWNhdGU=\",\n            ],\n            self.cert_extensions_get(self.logger, cert, recode=True),\n        )\n        self.assertFalse(mock_py.called)\n\n    @patch(\"acme_srv.helpers.certificates.cert_extensions_py_openssl_get\")\n    @patch(\"acme_srv.helpers.certificates.cryptography_version_get\")\n    def test_188_helper_cert_extensions_get(self, mock_version, mock_py):\n        \"\"\"test cert_san_get for a single SAN and recode = True\"\"\"\n        cert = \"MIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUyMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrdFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytmVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/fZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDeNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfythBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMBAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQRMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eAXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3akAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7WzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1IxrdBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6lEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKWJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1kqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWDSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2CUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0klGUNHG98CtsmlhrivhSTJWqSIOfyKGF\"\n        mock_version.return_value = 34\n        mock_py.return_value = [\"foo\", \"bar\"]\n        self.assertEqual(\n            [\"foo\", \"bar\"], self.cert_extensions_get(self.logger, cert, recode=True)\n        )\n        self.assertTrue(mock_py.called)\n\n    def test_189_helper_cert_extensions_py_openssl_get(self):\n        \"\"\"test cert_san_get for a single SAN and recode = False\"\"\"\n        cert = \"\"\"-----BEGIN CERTIFICATE-----\nMIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUy\nMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrd\nFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytm\nVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/f\nZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDe\nNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfyt\nhBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMB\nAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW\n2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQR\nMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIB\nDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eA\nXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3ak\nAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7\nWzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1Ixrd\nBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6\nlEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKW\nJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1\nkqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWD\nSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ\n2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2\nCUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0\nklGUNHG98CtsmlhrivhSTJWqSIOfyKGF\n-----END CERTIFICATE-----\"\"\"\n        self.assertEqual(\n            [\n                \"MAA=\",\n                \"BBQ3dnSIu/UUTXsMIMF+nFbY/7n6Qg==\",\n                \"AwIDuA==\",\n                \"MAoGCCsGAQUFBwMC\",\n                \"MA+CDWVzdGNsaWVudC5lc3Q=\",\n                \"AwIFoA==\",\n                \"Fg94Y2EgY2VydGlmaWNhdGU=\",\n            ],\n            self.cert_extensions_py_openssl_get(self.logger, cert, recode=False),\n        )\n\n    def test_190_cert_extensions_py_openssl_get(self):\n        \"\"\"test cert_san_get for a single SAN and recode = True\"\"\"\n        cert = \"MIIEZDCCAkygAwIBAgIIe941mx0FQtAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMTA0MDkxNTUyMDBaFw0yNjA0MDkxNTUyMDBaMBgxFjAUBgNVBAMTDWVzdGNsaWVudC5lc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAn6IqwTE1RvZUm3gelpu4tmrdFj8Ub98J1YeQz7qrew5iA81NeH9tR484edjcY0ieOt3e1MfxJoziWtaeqxpsfytmVB/i+850kVZmvRCR1jhW/4AzidkVBMQiCR5erPmmheeCxbKkto0rHb7ziRA+F8/fZLKfLNsahEQPxDuMItyQFCOQFHh8Hfuend2NgsQKeZ1r5Czf3n5Q6NFff7HG+MDeNDNdPB3ShgcvvNCFUS1z615/GIItfSqcWTAVaJ7436cA7yy5y4+0SvjfXYtHYfythBj/5UqlUmjni8Irj5K8uEtb1YUujmvlTTbzPkhYqIkSoyr7t21Dz+gcYn49AgMBAAGjgZ8wgZwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUN3Z0iLv1FE17DCDBfpxW2P+5+kIwCwYDVR0PBAQDAgO4MBMGA1UdJQQMMAoGCCsGAQUFBwMCMBgGA1UdEQQRMA+CDWVzdGNsaWVudC5lc3QwEQYJYIZIAYb4QgEBBAQDAgWgMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBACMAHHH4/0eAXbS/uKsIjLN1QPnnzgjxC0xoUon8UVM0PUMH+FMg6rs21Xyl5tn5iItmvKI9c3akAZ00RUQKVdmJVFRUKywmfF7n5epBpXtWJrSH817NT9GOp+PO5VUTDV5VkvpVLoy7WzThrheLKz1nC1dWowRz86tcBLAsC1zT17fsNZXQDuv4LiQQXs7QKhUU75r1IxrdBPeBQSP5skGpWxm8sapQSfOALoXu1pSoGIr6tqvNGuEoZGvUuWeQHG/G8c2ufL+6lEzZBBCd6e2tErkqD/vqfCRzbLcGgSPX0HVWdkjH09nHWXI5UhNr2YgGF7YvSTKWJfbDVlTql1BuSn2yTQtDk4E8k9BLr8WfqFSZvYrivT9Ax1n3BD9jvQL5+QRdioH1kqNGMme0Pb43pHciX4hu9L5rGenZRmxeGXZ78uSOR+n2bGxAMw1OY7Rx/lsNSKWDSN+7xIrwjjXO5Uthev1ecrLAK2+EpjITa6Y85ms39V4ypCEdujkKEBeVxuN8DdMJ2GaFGluSRZeYZ0LAPfYr5sp6G6904WF+PcT0WjGenH4PJLXrAttbhhvQxXU0Q8s2CUwUHy5OT/DW3POq7WETc+zmFGwZqiP3W9gmN0hHXsKqkNmz2RYgoH57lPS1PJb0klGUNHG98CtsmlhrivhSTJWqSIOfyKGF\"\n        self.assertEqual(\n            [\n                \"MAA=\",\n                \"BBQ3dnSIu/UUTXsMIMF+nFbY/7n6Qg==\",\n                \"AwIDuA==\",\n                \"MAoGCCsGAQUFBwMC\",\n                \"MA+CDWVzdGNsaWVudC5lc3Q=\",\n                \"AwIFoA==\",\n                \"Fg94Y2EgY2VydGlmaWNhdGU=\",\n            ],\n            self.cert_extensions_py_openssl_get(self.logger, cert, recode=True),\n        )\n\n    def test_191_csr_dn_get(self):\n        \"\"\" \" test csr_dn_get\"\"\"\n        csr = \"MIICjDCCAXQCAQAwFzEVMBMGA1UEAwwMdGVzdF9yZXF1ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6VRYaXuLS/DPa+pf5IEwycpjPfZ2vTFlvjvhwu9A3yaQQn4kD33Fu4p+zorIVmsjgpkUel2104lxFeSV081YKGzOtsajzaIRZhF7mHG5aVA8cahVPHlnxT06kO8F545ZsxE6T22tCbrLJpZk4hcaQUmGcZDWZqI7CXhbi1LSuIVIAAF0lTGMsanIM97ZEtA9mhtxFd7TsLlJpmls1l8MTavFcBtAZXqAsi4LnzEbozSjaLnuXsTe7tPmOS0uOLX+EcTAH/SxkbIg3whehTzC/sVmz5STbpklq3QuudtUl/509fpSa/UQ+WFOUUC3GhiiMM813ZsbAnt1BJepKtrfQIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHREEFjAUghJ0ZXN0X3JlcXVlc3QubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAFcKxjJXHBVjzqF3e6fCkDbF1JnVtNyDxZB+h4b5lI7SIuA9O/+0hcl/njeFB1gJbRODws10kKkiAYLXvS/fsLJg1gdyFPmDiCd2nJhDUCBcGmVYraGhV45x67jcUmoeqSSj5KyUY9zI+v3nANvZMf+g31ORtW8PuspkiiLJiyuGzFS67DGovbcBRrM67IApO7p04VwLA0hssFUa+wF9PUWIyu9TLx+w0rNYcp3d1wkJ905TB8gwOKXeB0RwkporlOF3KEcT+ueKZE04867bjZ/ZpiuIDFnO23MsUKLKU9ebWgwYN/xzxA8sroM69y+Acpt9Zwn3vRjVlT92Ztl218Q=\"\n        self.assertEqual(\"CN=test_request\", self.csr_dn_get(self.logger, csr))\n\n    def test_192_logger_setup(self):\n        \"\"\"logger setup\"\"\"\n        self.assertTrue(self.logger_setup(False))\n\n    def test_193_logger_setup(self):\n        \"\"\"logger setup\"\"\"\n        self.assertTrue(self.logger_setup(True))\n\n    @patch(\"acme_srv.helpers.logging_utils.load_config\")\n    def test_194_logger_setup(self, mock_load_cfg):\n        \"\"\"logger setup\"\"\"\n        mock_load_cfg.return_value = {\n            \"Helper\": {\n                \"log_format\": \"%(asctime)s - acme2certifier - %(levelname)s - %(message)s\"\n            }\n        }\n        self.assertTrue(self.logger_setup(True))\n\n    @patch(\"configparser.RawConfigParser\")\n    def test_195_load_config(self, mock_parser):\n        \"\"\"load config\"\"\"\n        self.assertTrue(self.load_config(None, None, None))\n\n    @patch(\"configparser.RawConfigParser\")\n    def test_196_load_config(self, mock_parser):\n        \"\"\"load config\"\"\"\n        self.assertTrue(self.load_config(self.logger, None, None))\n\n    @patch.dict(\"os.environ\", {\"ACME_SRV_CONFIGFILE\": \"ACME_SRV_CONFIGFILE\"})\n    @patch(\"configparser.RawConfigParser\")\n    def test_197_load_config(self, mock_parser):\n        \"\"\"load config\"\"\"\n        self.assertTrue(self.load_config(None, None, None))\n\n    def test_198_logger_info(self):\n        \"\"\"logger info\"\"\"\n        addr = \"addr\"\n        url = \"url\"\n        data_dic = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\"INFO:test_a2c:addr url {'foo': 'bar'}\", lcm.output)\n\n    def test_199_logger_info(self):\n        \"\"\"logger info replace remove Nonce in header\"\"\"\n        addr = \"addr\"\n        url = \"url\"\n        data_dic = {\"foo\": \"bar\", \"header\": {\"Replay-Nonce\": \"Replay-Nonce\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:addr url {'foo': 'bar', 'header': {'Replay-Nonce': '- modified -'}}\",\n            lcm.output,\n        )\n\n    def test_200_logger_info(self):\n        \"\"\"logger info replace remnove cert\"\"\"\n        addr = \"addr\"\n        url = \"/acme/cert/secret\"\n        data_dic = {\"foo\": \"bar\", \"data\": {\"Replay-Nonce\": \"Replay-Nonce\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:addr /acme/cert/secret {'foo': 'bar', 'data': ' - certificate - '}\",\n            lcm.output,\n        )\n\n    def test_201_logger_info(self):\n        \"\"\"logger info replace remove token\"\"\"\n        addr = \"addr\"\n        url = \"url\"\n        data_dic = {\"foo\": \"bar\", \"data\": {\"token\": \"token\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:addr url {'foo': 'bar', 'data': {'token': '- modified -'}}\",\n            lcm.output,\n        )\n\n    def test_202_logger_info(self):\n        \"\"\"logger info replace remove single token in challenges\"\"\"\n        addr = \"addr\"\n        url = \"url\"\n        data_dic = {\n            \"foo\": \"bar\",\n            \"data\": {\"challenges\": [{\"foo1\": \"bar1\", \"token\": \"token1\"}]},\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:addr url {'foo': 'bar', 'data': {'challenges': [{'foo1': 'bar1', 'token': '- modified - '}]}}\",\n            lcm.output,\n        )\n\n    def test_203_logger_info(self):\n        \"\"\"logger info replace remove two token in challenges\"\"\"\n        addr = \"addr\"\n        url = \"url\"\n        data_dic = {\n            \"foo\": \"bar\",\n            \"data\": {\n                \"challenges\": [\n                    {\"foo1\": \"bar1\", \"token\": \"token1\"},\n                    {\"foo2\": \"bar2\", \"token\": \"token1\"},\n                ]\n            },\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.logger_info(self.logger, addr, url, data_dic)\n        self.assertIn(\n            \"INFO:test_a2c:addr url {'foo': 'bar', 'data': {'challenges': [{'foo1': 'bar1', 'token': '- modified - '}, {'foo2': 'bar2', 'token': '- modified - '}]}}\",\n            lcm.output,\n        )\n\n    @patch(\"builtins.print\")\n    def test_204_print_debug(self, mock_print):\n        \"\"\"test print_debug\"\"\"\n        self.print_debug(False, \"test\")\n        self.assertFalse(mock_print.called)\n\n    @patch(\"builtins.print\")\n    def test_205_print_debug(self, mock_print):\n        \"\"\"test print_debug\"\"\"\n        self.print_debug(True, \"test\")\n        self.assertTrue(mock_print.called)\n\n    def test_206_jwk_thumbprint_get(self):\n        \"\"\"test jwk_thumbprint_get with empty pubkey\"\"\"\n        pub_key = None\n        self.assertFalse(self.jwk_thumbprint_get(self.logger, pub_key))\n\n    @patch(\"jwcrypto.jwk.JWK\")\n    def test_207_jwk_thumbprint_get(self, mock_jwk):\n        \"\"\"test jwk_thumbprint_get with  pubkey\"\"\"\n        pub_key = {\"pub_key\": \"pub_key\"}\n        mock_jwk = Mock()\n        self.assertTrue(self.jwk_thumbprint_get(self.logger, pub_key))\n\n    @patch(\"jwcrypto.jwk.JWK\")\n    def test_208_jwk_thumbprint_get(self, mock_jwk):\n        \"\"\"test jwk_thumbprint_get with  pubkey\"\"\"\n        pub_key = {\"pub_key\": \"pub_key\"}\n        mock_jwk.side_effect = Exception(\"exc_jwk_jwk\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.jwk_thumbprint_get(self.logger, pub_key))\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get the JWKEY thumbprint from public key: exc_jwk_jwk\",\n            lcm.output,\n        )\n\n    @patch(\"socket.AF_INET\")\n    def test_209_allowed_gai_family(self, mock_sock):\n        \"\"\"test allowed_gai_family\"\"\"\n        self.assertTrue(self.allowed_gai_family())\n\n    def test_210_validate_csr(self):\n        \"\"\"patched_create_connection\"\"\"\n        self.assertTrue(self.validate_csr(self.logger, \"oder_dic\", \"csr\"))\n\n    @patch(\"acme_srv.helpers.network.proxystring_convert\")\n    @patch(\"ssl.DER_cert_to_PEM_cert\")\n    @patch(\"ssl.SSLContext.wrap_socket\")\n    @patch(\"socks.socksocket\")\n    def test_211_servercert_get(self, mock_sock, mock_context, mock_cert, mock_convert):\n        \"\"\"test servercert get\"\"\"\n        mock_convert.return_value = (\"proxy_proto\", \"proxy_addr\", \"proxy_port\")\n        mock_sock = Mock()\n        mock_context = Mock()\n        mock_cert.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.servercert_get(self.logger, \"hostname\"))\n        self.assertFalse(mock_convert.called)\n\n    @patch(\"acme_srv.helpers.network.ipv6_chk\")\n    @patch(\"acme_srv.helpers.network.proxystring_convert\")\n    @patch(\"ssl.DER_cert_to_PEM_cert\")\n    @patch(\"ssl.SSLContext.wrap_socket\")\n    @patch(\"socket.socket\")\n    @patch(\"socks.socksocket\")\n    def test_212_servercert_get(\n        self, mock_sock, mock_ssock, mock_context, mock_cert, mock_convert, mock_ipchk\n    ):\n        \"\"\"test servercert get ippv6\"\"\"\n        mock_convert.return_value = (\"proxy_proto\", \"proxy_addr\", \"proxy_port\")\n        mock_ipchk.return_value = True\n        mock_context = Mock()\n        mock_cert.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.servercert_get(self.logger, \"hostname\"))\n        self.assertTrue(mock_ssock.called)\n        self.assertFalse(mock_sock.called)\n\n    @patch(\"acme_srv.helpers.network.proxystring_convert\")\n    @patch(\"ssl.DER_cert_to_PEM_cert\")\n    @patch(\"ssl.SSLContext.wrap_socket\")\n    @patch(\"socket.socket\")\n    @patch(\"socks.socksocket\")\n    def test_213_servercert_get(\n        self, mock_sock, mock_ssock, mock_context, mock_cert, mock_convert\n    ):\n        \"\"\"test servercert get with proxy\"\"\"\n        mock_convert.return_value = (\"proxy_proto\", \"proxy_addr\", \"proxy_port\")\n        mock_context = Mock()\n        mock_cert.return_value = \"foo\"\n        self.assertEqual(\n            \"foo\", self.servercert_get(self.logger, \"hostname\", 443, \"proxy\")\n        )\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_ssock.called)\n\n    @patch(\"ssl.DER_cert_to_PEM_cert\")\n    @patch(\"ssl.SSLContext.wrap_socket\")\n    @patch(\"socks.socksocket\")\n    def test_214_servercert_get(self, mock_sock, mock_context, mock_cert):\n        \"\"\"test servercert exception\"\"\"\n        mock_sock = Mock()\n        mock_context.side_effect = Exception(\"exc_warp_sock\")\n        mock_cert.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.servercert_get(self.logger, \"hostname\", 443))\n        self.assertFalse(mock_cert.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get peer certificate. Error: exc_warp_sock\",\n            lcm.output,\n        )\n\n    @patch(\"ssl.TLSVersion\")\n    @patch(\"acme_srv.helpers.network.proxystring_convert\")\n    @patch(\"ssl.DER_cert_to_PEM_cert\")\n    @patch(\"ssl.SSLContext.wrap_socket\")\n    @patch(\"socks.socksocket\")\n    def test_215_servercert_get(\n        self, mock_sock, mock_context, mock_cert, mock_convert, map_min_version\n    ):\n        \"\"\"test servercert get\"\"\"\n        mock_convert.return_value = (\"proxy_proto\", \"proxy_addr\", \"proxy_port\")\n        mock_sock = Mock()\n        mock_context = Mock()\n        mock_cert.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"foo\", self.servercert_get(self.logger, \"hostname\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting the peer certifiate: minimum tls version not supported\",\n            lcm.output,\n        )\n        self.assertFalse(mock_convert.called)\n\n    @patch(\"dns.resolver.Resolver\")\n    @patch(\"dns.resolver.resolve\")\n    def test_216_txt_get(self, mock_resolve, mock_res):\n        \"\"\"successful dns-query returning one txt record\"\"\"\n        resp_obj = Mock()\n        resp_obj.strings = [\"foo\", \"bar\"]\n        mock_resolve.return_value = [resp_obj]\n        self.assertEqual([\"foo\"], self.txt_get(self.logger, \"foo\", \"10.0.0.1\"))\n        self.assertTrue(mock_res.called)\n\n    @patch(\"dns.resolver.resolve\")\n    def test_217_txt_get(self, mock_resolve):\n        \"\"\"successful dns-query returning one txt record\"\"\"\n        resp_obj = Mock()\n        resp_obj.strings = [\"foo\", \"bar\"]\n        mock_resolve.return_value = [resp_obj]\n        self.assertEqual([\"foo\"], self.txt_get(self.logger, \"foo\"))\n\n    @patch(\"dns.resolver.resolve\")\n    def test_218_txt_get(self, mock_resolve):\n        \"\"\"successful dns-query returning one txt record\"\"\"\n        resp_obj1 = Mock()\n        resp_obj1.strings = [\"foo1\", \"bar1\"]\n        resp_obj2 = Mock()\n        resp_obj2.strings = [\"foo2\", \"bar2\"]\n        mock_resolve.return_value = [resp_obj1, resp_obj2]\n        self.assertEqual([\"foo1\", \"foo2\"], self.txt_get(self.logger, \"foo\"))\n\n    @patch(\"dns.resolver.resolve\")\n    def test_219_txt_get(self, mock_resolve):\n        \"\"\"successful dns-query returning one txt record\"\"\"\n        mock_resolve.side_effect = Exception(\"mock_resolve\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.txt_get(self.logger, \"foo\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Could not get TXT record: mock_resolve\", lcm.output\n        )\n\n    def test_220_proxystring_convert(self):\n        \"\"\"convert proxy_string http\"\"\"\n        self.assertEqual(\n            (3, \"proxy\", 8080),\n            self.proxystring_convert(self.logger, \"http://proxy:8080\"),\n        )\n\n    def test_221_proxystring_convert(self):\n        \"\"\"convert proxy_string socks4\"\"\"\n        self.assertEqual(\n            (1, \"proxy\", 8080),\n            self.proxystring_convert(self.logger, \"socks4://proxy:8080\"),\n        )\n\n    def test_222_proxystring_convert(self):\n        \"\"\"convert proxy_string socks5\"\"\"\n        self.assertEqual(\n            (2, \"proxy\", 8080),\n            self.proxystring_convert(self.logger, \"socks5://proxy:8080\"),\n        )\n\n    def test_223_proxystring_convert(self):\n        \"\"\"convert proxy_string unknown protocol\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, \"proxy\", 8080),\n                self.proxystring_convert(self.logger, \"unk://proxy:8080\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Unknown proxy protocol: unk\",\n            lcm.output,\n        )\n\n    def test_224_proxystring_convert(self):\n        \"\"\"convert proxy_string unknown protocol\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (3, \"proxy\", None),\n                self.proxystring_convert(self.logger, \"http://proxy:ftp\"),\n            )\n        self.assertIn(\"ERROR:test_a2c:Unknown proxy port: ftp\", lcm.output)\n\n    def test_225_proxystring_convert(self):\n        \"\"\"convert proxy_string porxy sting without protocol\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None), self.proxystring_convert(self.logger, \"proxy\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Error while splitting proxy_server string: proxy\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:proxy_proto (None), proxy_addr (None) or proxy_port (None) missing\",\n            lcm.output,\n        )\n\n    def test_226_proxystring_convert(self):\n        \"\"\"convert proxy_string porxy sting without port\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None),\n                self.proxystring_convert(self.logger, \"http://proxy\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Error while splitting proxy into host/port: proxy\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:proxy_proto (http), proxy_addr (None) or proxy_port (None) missing\",\n            lcm.output,\n        )\n\n    def test_227_proxy_check(self):\n        \"\"\"check proxy for empty list\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {}\n        self.assertFalse(self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_228_proxy_check(self):\n        \"\"\"check proxy - no match\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"foo1.bar.local\": \"proxy_match\"}\n        self.assertFalse(self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_229_proxy_check(self):\n        \"\"\"check proxy - single entry\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"foo.bar.local\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_230_proxy_check(self):\n        \"\"\"check proxy  - multiple entry\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"bar.bar.local\": \"proxy_nomatch\", \"foo.bar.local\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_231_proxy_check(self):\n        \"\"\"check proxy  -  multiple entrie domain match\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"bar.bar.local\": \"proxy_nomatch\", \"bar.local$\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_232_proxy_check(self):\n        \"\"\"check proxy for empty list  multiple entrie domain match\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"bar.local$\": \"proxy_nomatch\", \"foo.bar.local$\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_233_proxy_check(self):\n        \"\"\"check proxy - multiple entrie domain match\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"bar.local$\": \"proxy_match\", \"foo1.bar.local$\": \"proxy_nomatch\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_234_proxy_check(self):\n        \"\"\"check proxy - wildcard\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"foo1.bar.local$\": \"proxy_nomatch\", \"*.bar.local$\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_235_proxy_check(self):\n        \"\"\"check proxy - wildcard\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\".local$\": \"proxy_nomatch\", \"*.bar.local$\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_236_proxy_check(self):\n        \"\"\"check proxy - wildcard\"\"\"\n        fqdn = \"local\"\n        proxy_list = {\"local$\": \"proxy_match\", \"*.bar.local$\": \"proxy_no_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_237_proxy_check(self):\n        \"\"\"check proxy - wildcard\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\n            \"*\": \"wildcard\",\n            \"notlocal$\": \"proxy_no_match\",\n            \"*.notbar.local$\": \"proxy_no_match\",\n        }\n        self.assertEqual(\"wildcard\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    @patch(\"sys.__excepthook__\")\n    def test_238_handle_exception_keyboard_interrupt(self, mock_excepthook):\n        \"\"\"test handle_exception with KeyboardInterrupt - should call sys.__excepthook__\"\"\"\n        exc_type = KeyboardInterrupt\n        exc_value = KeyboardInterrupt(\"Test keyboard interrupt\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that sys.__excepthook__ was called with correct parameters\n        mock_excepthook.assert_called_once_with(exc_type, exc_value, exc_traceback)\n        # Verify function returned None (early return)\n        self.assertIsNone(result)\n\n    @patch(\"logging.exception\")\n    def test_239_handle_exception_regular_exception(self, mock_logging_exception):\n        \"\"\"test handle_exception with regular exception - should call logging.exception\"\"\"\n        exc_type = ValueError\n        exc_value = ValueError(\"Test value error\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that logging.exception was called with correct parameters\n        mock_logging_exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    @patch(\"logging.exception\")\n    def test_240_handle_exception_runtime_error(self, mock_logging_exception):\n        \"\"\"test handle_exception with RuntimeError - should call logging.exception\"\"\"\n        exc_type = RuntimeError\n        exc_value = RuntimeError(\"Test runtime error\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that logging.exception was called\n        mock_logging_exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    @patch(\"logging.exception\")\n    def test_241_handle_exception_type_error(self, mock_logging_exception):\n        \"\"\"test handle_exception with TypeError - should call logging.exception\"\"\"\n        exc_type = TypeError\n        exc_value = TypeError(\"Test type error\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that logging.exception was called\n        mock_logging_exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    @patch(\"sys.__excepthook__\")\n    @patch(\"logging.exception\")\n    def test_242_handle_exception_keyboard_interrupt_subclass(\n        self, mock_logging_exception, mock_excepthook\n    ):\n        \"\"\"test handle_exception with KeyboardInterrupt subclass - should call sys.__excepthook__\"\"\"\n        # Create a subclass of KeyboardInterrupt\n        class CustomKeyboardInterrupt(KeyboardInterrupt):\n            pass\n\n        exc_type = CustomKeyboardInterrupt\n        exc_value = CustomKeyboardInterrupt(\"Custom keyboard interrupt\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that sys.__excepthook__ was called (not logging.exception)\n        mock_excepthook.assert_called_once_with(exc_type, exc_value, exc_traceback)\n        mock_logging_exception.assert_not_called()\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    @patch(\"logging.exception\")\n    def test_243_handle_exception_system_exit(self, mock_logging_exception):\n        \"\"\"test handle_exception with SystemExit - should call logging.exception\"\"\"\n        exc_type = SystemExit\n        exc_value = SystemExit(1)\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that logging.exception was called (SystemExit is not KeyboardInterrupt)\n        mock_logging_exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    @patch(\"logging.exception\")\n    def test_244_handle_exception_custom_exception(self, mock_logging_exception):\n        \"\"\"test handle_exception with custom exception - should call logging.exception\"\"\"\n        # Create a custom exception class\n        class CustomException(Exception):\n            pass\n\n        exc_type = CustomException\n        exc_value = CustomException(\"Custom exception message\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that logging.exception was called\n        mock_logging_exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        # Verify function returned None\n        self.assertIsNone(result)\n\n    def test_245_proxy_check(self):\n        \"\"\"check proxy - wildcard\"\"\"\n        fqdn = \"foo.bar.local\"\n        proxy_list = {\"*.bar.local$\": \"proxy_match\"}\n        self.assertEqual(\"proxy_match\", self.proxy_check(self.logger, fqdn, proxy_list))\n\n    def test_246_ca_handler_load(self):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"foo\": \"bar\"}\n        self.assertFalse(self.ca_handler_load(self.logger, config_dic))\n\n    def test_247_ca_handler_load(self):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.ca_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler configuration missing in config file\", lcm.output\n        )\n\n    @patch(\"importlib.import_module\")\n    def test_248_ca_handler_load(self, mock_imp):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"CAhandler\": {\"foo\": \"bar\"}}\n        mock_imp.side_effect = Exception(\"exc_mock_imp\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.ca_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading default CAhandler failed with err: exc_mock_imp\",\n            lcm.output,\n        )\n\n    @patch(\"importlib.import_module\")\n    def test_249_ca_handler_load(self, mock_imp):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"CAhandler\": {\"foo\": \"bar\"}}\n        mock_imp.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.ca_handler_load(self.logger, config_dic))\n\n    @patch(\"importlib.util\")\n    def test_250_ca_handler_load(self, mock_util):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"CAhandler\": {\"handler_file\": \"foo\"}}\n        mock_util.module_from_spec = Mock(return_value=\"foo\")\n        self.assertEqual(\"foo\", self.ca_handler_load(self.logger, config_dic))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"importlib.util\")\n    def test_251_ca_handler_load(self, mock_util, mock_imp):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"CAhandler\": {\"handler_file\": \"foo\"}}\n        mock_util.module_from_spec.side_effect = Exception(\"exc_mock_util\")\n        mock_imp.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"foo\", self.ca_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading CAhandler configured in cfg failed with err: exc_mock_util\",\n            lcm.output,\n        )\n\n    @patch(\"importlib.import_module\")\n    @patch(\"importlib.util\")\n    def test_252_ca_handler_load(self, mock_util, mock_imp):\n        \"\"\"test ca_handler_load\"\"\"\n        config_dic = {\"CAhandler\": {\"handler_file\": \"foo\"}}\n        mock_util.module_from_spec.side_effect = Exception(\"exc_mock_util\")\n        mock_imp.side_effect = Exception(\"exc_mock_imp\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.ca_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading default CAhandler failed with err: exc_mock_imp\",\n            lcm.output,\n        )\n\n    def test_253_eab_handler_load(self):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eab_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:EABhandler configuration missing in config file\", lcm.output\n        )\n\n    @patch(\"importlib.import_module\")\n    def test_254_eab_handler_load(self, mock_imp):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"EABhandler\": {\"foo\": \"bar\"}}\n        mock_imp.side_effect = Exception(\"exc_mock_imp\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eab_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading default EABhandler failed with err: exc_mock_imp\",\n            lcm.output,\n        )\n\n    @patch(\"importlib.import_module\")\n    def test_255_eab_handler_load(self, mock_imp):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"EABhandler\": {\"foo\": \"bar\"}}\n        mock_imp.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.eab_handler_load(self.logger, config_dic))\n\n    @patch(\"importlib.util\")\n    def test_256_eab_handler_load(self, mock_util):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"EABhandler\": {\"eab_handler_file\": \"foo\"}}\n        mock_util.module_from_spec = Mock(return_value=\"foo\")\n        self.assertEqual(\"foo\", self.eab_handler_load(self.logger, config_dic))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"importlib.util\")\n    def test_257_eab_handler_load(self, mock_util, mock_imp):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"EABhandler\": {\"eab_handler_file\": \"foo\"}}\n        mock_util.module_from_spec.side_effect = Exception(\"exc_mock_util\")\n        mock_imp.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"foo\", self.eab_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading EABhandler configured in cfg failed with err: exc_mock_util\",\n            lcm.output,\n        )\n\n    @patch(\"importlib.import_module\")\n    @patch(\"importlib.util\")\n    def test_258_eab_handler_load(self, mock_util, mock_imp):\n        \"\"\"test eab_handler_load\"\"\"\n        config_dic = {\"EABhandler\": {\"eab_handler_file\": \"foo\"}}\n        mock_util.module_from_spec.side_effect = Exception(\"exc_mock_util\")\n        mock_imp.side_effect = Exception(\"exc_mock_imp\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.eab_handler_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading default EABhandler failed with err: exc_mock_imp\",\n            lcm.output,\n        )\n\n    def test_259_hooks_load(self):\n        \"\"\"test hooks load with empty config_dic\"\"\"\n        config_dic = {}\n        self.assertFalse(self.hooks_load(self.logger, config_dic))\n\n    def test_260_hooks_load(self):\n        \"\"\"test hooks load with hooks but no hooks_file in config_dic\"\"\"\n        config_dic = {\"Hooks\": {\"foo\": \"bar\"}}\n        self.assertFalse(self.hooks_load(self.logger, config_dic))\n\n    @patch(\"importlib.util\")\n    def test_261_hooks_load(self, mock_util):\n        \"\"\"test hooks load with hooks but no hooks_file in  config_dic\"\"\"\n        config_dic = {\"Hooks\": {\"hooks_file\": \"bar\"}}\n        mock_util.module_from_spec = Mock(return_value=\"foo\")\n        self.assertEqual(\"foo\", self.hooks_load(self.logger, config_dic))\n        self.assertTrue(mock_util.spec_from_file_location.called)\n        self.assertTrue(mock_util.module_from_spec.called)\n\n    @patch(\"importlib.util\")\n    def test_262_hooks_load(self, mock_util):\n        \"\"\"test hooks load with hooks but no hooks_file in  config_dic\"\"\"\n        config_dic = {\"Hooks\": {\"hooks_file\": \"bar\"}}\n        mock_util.module_from_spec = Exception(\"exc_mock_util\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.hooks_load(self.logger, config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading Hooks configured in cfg failed with err: 'Exception' object is not callable\",\n            lcm.output,\n        )\n\n    def test_263_error_dic_get(self):\n        \"\"\"test error_dic_get\"\"\"\n        result = {\n            \"accountdoesnotexist\": \"urn:ietf:params:acme:error:accountDoesNotExist\",\n            \"alreadyrevoked\": \"urn:ietf:params:acme:error:alreadyRevoked\",\n            \"badcsr\": \"urn:ietf:params:acme:error:badCSR\",\n            \"badpubkey\": \"urn:ietf:params:acme:error:badPublicKey\",\n            \"badrevocationreason\": \"urn:ietf:params:acme:error:badRevocationReason\",\n            \"externalaccountrequired\": \"urn:ietf:params:acme:error:externalAccountRequired\",\n            \"invalidcontact\": \"urn:ietf:params:acme:error:invalidContact\",\n            \"invalidprofile\": \"urn:ietf:params:acme:error:invalidProfile\",\n            \"malformed\": \"urn:ietf:params:acme:error:malformed\",\n            \"ordernotready\": \"urn:ietf:params:acme:error:orderNotReady\",\n            \"ratelimited\": \"urn:ietf:params:acme:error:rateLimited\",\n            \"rejectedidentifier\": \"urn:ietf:params:acme:error:rejectedIdentifier\",\n            \"serverinternal\": \"urn:ietf:params:acme:error:serverInternal\",\n            \"unauthorized\": \"urn:ietf:params:acme:error:unauthorized\",\n            \"unsupportedidentifier\": \"urn:ietf:params:acme:error:unsupportedIdentifier\",\n            \"useractionrequired\": \"urn:ietf:params:acme:error:userActionRequired\",\n        }\n        self.assertEqual(result, self.error_dic_get(self.logger))\n\n    def test_264_logger_nonce_modify(self):\n        \"\"\"test _logger_nonce_modify()\"\"\"\n        data_dic = {\"foo\": \"bar\"}\n        self.assertEqual({\"foo\": \"bar\"}, self.logger_nonce_modify(data_dic))\n\n    def test_265_logger_nonce_modify(self):\n        \"\"\"test _logger_nonce_modify()\"\"\"\n        data_dic = {\"foo\": \"bar\", \"header\": {\"foo\": \"bar\"}}\n        self.assertEqual(\n            {\"foo\": \"bar\", \"header\": {\"foo\": \"bar\"}}, self.logger_nonce_modify(data_dic)\n        )\n\n    def test_266_logger_nonce_modify(self):\n        \"\"\"test _logger_nonce_modify()\"\"\"\n        data_dic = {\"foo\": \"bar\", \"header\": {\"Replay-Nonce\": \"bar\"}}\n        self.assertEqual(\n            {\"foo\": \"bar\", \"header\": {\"Replay-Nonce\": \"- modified -\"}},\n            self.logger_nonce_modify(data_dic),\n        )\n\n    def test_267_logger_certificate_modify(self):\n        \"\"\"test _logger_certificate_modify()\"\"\"\n        data_dic = {\"data\": \"bar\"}\n        self.assertEqual(\n            {\"data\": \"bar\"}, self.logger_certificate_modify(data_dic, \"locator\")\n        )\n\n    def test_268_logger_certificate_modify(self):\n        \"\"\"test _logger_certificate_modify()\"\"\"\n        data_dic = {\"data\": \"bar\"}\n        self.assertEqual(\n            {\"data\": \" - certificate - \"},\n            self.logger_certificate_modify(data_dic, \"foo/acme/cert\"),\n        )\n\n    def test_269_logger_token_modify(self):\n        \"\"\"test _logger_token_modify()\"\"\"\n        data_dic = {\"data\": \"bar\"}\n        self.assertEqual({\"data\": \"bar\"}, self.logger_token_modify(data_dic))\n\n    def test_270_logger_token_modify(self):\n        \"\"\"test _logger_token_modify()\"\"\"\n        data_dic = {\"data\": {\"token\": \"token\"}}\n        self.assertEqual(\n            {\"data\": {\"token\": \"- modified -\"}}, self.logger_token_modify(data_dic)\n        )\n\n    def test_271_logger_challenges_modify(self):\n        \"\"\"test _logger_challenges_modify()\"\"\"\n        data_dic = {\"data\": \"bar\"}\n        self.assertEqual({\"data\": \"bar\"}, self.logger_challenges_modify(data_dic))\n\n    def test_272_logger_challenges_modify(self):\n        \"\"\"test _logger_challenges_modify()\"\"\"\n        data_dic = {\"data\": {\"challenges\": [{\"token\": \"token1\"}]}}\n        self.assertEqual(\n            {\"data\": {\"challenges\": [{\"token\": \"- modified - \"}]}},\n            self.logger_challenges_modify(data_dic),\n        )\n\n    def test_273_logger_challenges_modify(self):\n        \"\"\"test _logger_challenges_modify()\"\"\"\n        data_dic = {\"data\": {\"challenges\": [{\"token\": \"token1\"}, {\"token\": \"token2\"}]}}\n        self.assertEqual(\n            {\n                \"data\": {\n                    \"challenges\": [\n                        {\"token\": \"- modified - \"},\n                        {\"token\": \"- modified - \"},\n                    ]\n                }\n            },\n            self.logger_challenges_modify(data_dic),\n        )\n\n    def test_274_config_check(self):\n        \"\"\"test config check\"\"\"\n        config_dic = {\"foo\": {\"bar\": '\"foobar\"'}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.config_check(self.logger, config_dic)\n        self.assertIn(\n            'WARNING:test_a2c:Section foo option: bar contains \" characters. Please check if this is required!',\n            lcm.output,\n        )\n\n    def test_275_helper_cert_cn_get(self):\n        \"\"\"get cn of csr\"\"\"\n        cert = \"\"\"MIIDDTCCAfWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9mb28u\n                ZXhhbXBsZS5jb20wHhcNMTkwMTIwMTY1OTIwWhcNMTkwMjE5MTY1OTIwWjAaMRgw\n                FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n                ggEKAoIBAQCqUeNzDyBVugUKZq597ishYAdMPgus5Nw5pWE/Jw7PP0koeFE2wODq\n                HVb+XNFFEX4IOyiE2Pi4ilzfXYGKchhP3wHgnkxGNIwt/cDNZgyTiUpITV/ciFaC\n                7avkvQS6ScCYUYrhby7QnvcU02mAyhNcSVGI5TW7HhFdtWrEAK3N8H6yhxHLSi2y\n                dpQ3kCJyJylqt/Rv3uKNjCvTv867K6A1QSsXoAxtPK9P0UOTRvgHkFf8T32Bn/Er\n                1bjkX9Ms8rqDQmicCWJk260lUHzN6vxaeiEg7Kz3TA8Ik3DMIcvwJrE168G1APo+\n                FyOIKyx+t78HWOlNINIqZMj5e2DpulV7AgMBAAGjXjBcMB8GA1UdIwQYMBaAFK1Z\n                zuGt0Pe+NLerCXqQBYmVV7suMB0GA1UdDgQWBBStWc7hrdD3vjS3qwl6kAWJlVe7\n                LjAaBgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEB\n                AANW0DD4Xp7LH/Rzf2jVLwiFlbtR6iazyn9S/pH2Gwqjkscv/27/dqJb7CfPdD02\n                5ItQcYkZPJhDOsj63kvUaD89QU31RnYQrXrbXFqYOIAq6kxfZUoQmpfEBxbB4Wxm\n                TW0OWS+FMqNw/SuGs6EQjTRA+gBOeGzj4H9yOFOg0PpadBayZ7UT4lm1LOiFHh8h\n                bta75ocePrurdNxsxKJhLlXbnKD6lurCb4khRhrmLmpK8JxhuaevEVklSQX0gqlR\n                fxAH4XQsaqcaedPNI+W5OUITMz40ezDCbUqxS9KEMCGPoOTXNRAjbr72sc4Vkw7H\n                t+eRUDECE+0UnjyeCjTn3EU=\"\"\"\n        self.assertEqual(\"foo.example.com\", self.cert_cn_get(self.logger, cert))\n\n    def test_276_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"foo\"\n        self.assertEqual(\"foo\", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_277_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"foo\\n;\"\n        self.assertEqual(\"foo;\", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_278_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"fooö\"\n        self.assertEqual(\"foo\", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_279_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"fooö\"\n        self.assertEqual(\"foo\", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_280_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"foo    \"\n        self.assertEqual(\"foo \", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_281_logger_challenges_modify(self):\n        \"\"\"test string_sanitize()\"\"\"\n        unsafe_string = \"foo\\u0009\"\n        self.assertEqual(\"foo \", self.string_sanitize(self.logger, unsafe_string))\n\n    def test_282_pembundle_to_list(self):\n        \"\"\"bundle to list\"\"\"\n        pembundle_to_list = \"foo\"\n        self.assertFalse(self.pembundle_to_list(self.logger, pembundle_to_list))\n\n    def test_283_pembundle_to_list(self):\n        \"\"\"bundle to list\"\"\"\n        pembundle_to_list = \"-----BEGIN CERTIFICATE-----foo\"\n        self.assertEqual(\n            [\"-----BEGIN CERTIFICATE-----foo\\n\"],\n            self.pembundle_to_list(self.logger, pembundle_to_list),\n        )\n\n    def test_284_pembundle_to_list(self):\n        \"\"\"bundle to list\"\"\"\n        pembundle_to_list = (\n            \"-----BEGIN CERTIFICATE-----foo\\n-----BEGIN CERTIFICATE-----foo1\"\n        )\n        self.assertEqual(\n            [\"-----BEGIN CERTIFICATE-----foo\\n\", \"-----BEGIN CERTIFICATE-----foo1\\n\"],\n            self.pembundle_to_list(self.logger, pembundle_to_list),\n        )\n\n    def test_285_certid_check(self):\n        \"\"\"test certid_check\"\"\"\n        certid = \"e181efbe6f7ae3ea71c78fc99e4226d7185715be3d289eaa56801dff4696ca4d0420ae0dcf53345691826b81d093e9c7588c35dd5ec5eacf5b1b2606330515d5faf402082cca85f640d54142\"\n        renewal_info = \"MFswCwYJYIZIAWUDBAIBBCDhge--b3rj6nHHj8meQibXGFcVvj0onqpWgB3_RpbKTQQgrg3PUzRWkYJrgdCT6cdYjDXdXsXqz1sbJgYzBRXV-vQCCCzKhfZA1UFC\"\n        self.assertTrue(self.certid_check(self.logger, renewal_info, certid))\n\n    def test_286_certid_check(self):\n        \"\"\"test certid_check\"\"\"\n        certid = \"false\"\n        renewal_info = \"MFswCwYJYIZIAWUDBAIBBCDhge--b3rj6nHHj8meQibXGFcVvj0onqpWgB3_RpbKTQQgrg3PUzRWkYJrgdCT6cdYjDXdXsXqz1sbJgYzBRXV-vQCCCzKhfZA1UFC\"\n        self.assertFalse(self.certid_check(self.logger, renewal_info, certid))\n\n    def test_287_certid_asn1_get(self):\n        \"\"\"test certid_asn1_get()\"\"\"\n\n        cert_pem = \"\"\"-----BEGIN CERTIFICATE-----\nMIIDijCCAXKgAwIBAgIILMqF9kDVQUIwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yMzA3MDMwNTA5\nNDVaFw0yNDA3MDIwNTA5NDVaMBsxGTAXBgNVBAMTEGxlZ28tMS5iYXIubG9jYWww\nWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQDmn49aWZb/mRghkT3rgpkV45c5PbE\nLFgQSh2qT7AHEmOv+8SNSjAbgysgJDqXMte4nUQOtYeKEZiy8xaD5ymho4GNMIGK\nMB0GA1UdDgQWBBS5fAkcGAyo4okkZxEeGgLqmLo3izAfBgNVHSMEGDAWgBS/3o6O\nBiIiq61DyN3UT6irSEE+1TALBgNVHQ8EBAMCA+gwDAYDVR0TAQH/BAIwADAtBgNV\nHREEJjAkghBsZWdvLTEuYmFyLmxvY2FsghBsZWdvLTIuYmFyLmxvY2FsMA0GCSqG\nSIb3DQEBCwUAA4ICAQAAYE1U/IR6XRbjnRT9jzit/biRJDFGT7JMfD14pUpXU7ax\nIfndaWA8y0UQ0ZyIiLke+chHWQ2CrYT7wUMjSp08ztViWXDg0IifW4Hcyqx/oNT0\npCaQeRHJOM17ai9oWZEaJMY/r0/1fCTAK7D0zrJxHCQqEXuosm9LJd0fRMamgGZC\nbXN/HrVOGojOLwzE1mMyW261hI5eU7/DD128iyc0mfeCi2R3lL7oXcwN7MtrKUYq\nqpBEfMlrf07zpAGVe/LOB6SLoPCbjYPC368mwdxgGLLz2+nqPTK2V+2yjylt3de5\nLVp6UG3ZxLNN2RjVXCE7Bh1fT585+NzaaXpf4SWyDxu11yHdfXP5Nw5paELjyNhM\nV9lnEJUiLB4scO1p4XWOQDboLXf0RbI0M/0IxqRZzzKxDRXsnIzdQOswxv8Jfnli\nr0yVc/vzYQeKEkKkRwRw2SVTj9v4lU+ryMrqMCpw/z6vRBLKWAg8cmGE2OTtcL91\nQvJeYpp+9g2GJxs+gEja3BlliLf6EUQkI/P/tCUoe3pEJ0XyPgl5m+5SdAS5Ic0U\nqaUvBXcmU56/h8pzSCF2RDoGEpZyHaG84VJLCV857QD2NFlE/S+tUAggbc5l66OE\nu3dZ8B+BJinV++0slP29NFdZ7m6ta0jZJfOaMXYyDwCYvD7FXygHu3fMwc5k3A==\n-----END CERTIFICATE-----\"\"\"\n\n        issuer_pem = \"\"\"-----BEGIN CERTIFICATE-----\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\njX1vlY35Ofonc4+6dRVamBiF9A==\n-----END CERTIFICATE-----\"\"\"\n        result = \"e181efbe6f7ae3ea71c78fc99e4226d7185715be3d289eaa56801dff4696ca4d0420ae0dcf53345691826b81d093e9c7588c35dd5ec5eacf5b1b2606330515d5faf402082cca85f640d54142\"\n        self.assertEqual(\n            result, self.certid_asn1_get(self.logger, cert_pem, issuer_pem)\n        )\n\n    def test_288_certid_hex_get(self):\n        \"\"\"test certid_check\"\"\"\n        certid = \"false\"\n        renewal_info = \"MFswCwYJYIZIAWUDBAIBBCDhge--b3rj6nHHj8meQibXGFcVvj0onqpWgB3_RpbKTQQgrg3PUzRWkYJrgdCT6cdYjDXdXsXqz1sbJgYzBRXV-vQCCCzKhfZA1UFC\"\n        self.assertEqual(\n            (\n                \"300b0609608648016503040201\",\n                \"e181efbe6f7ae3ea71c78fc99e4226d7185715be3d289eaa56801dff4696ca4d0420ae0dcf53345691826b81d093e9c7588c35dd5ec5eacf5b1b2606330515d5faf402082cca85f640d54142\",\n            ),\n            self.certid_hex_get(self.logger, renewal_info),\n        )\n\n    @patch(\"acme_srv.helpers.network.USER_AGENT\", \"FOOBAR\")\n    def test_289_v6_adjust(self):\n        \"\"\"test v6_adjust()\"\"\"\n        url = \"http://www.foo.bar\"\n        self.assertEqual(\n            (\n                {\n                    \"Connection\": \"close\",\n                    \"Accept-Encoding\": \"gzip\",\n                    \"User-Agent\": \"FOOBAR\",\n                },\n                \"http://www.foo.bar\",\n            ),\n            self.v6_adjust(self.logger, url),\n        )\n\n    @patch(\"acme_srv.helpers.network.USER_AGENT\", \"FOOBAR\")\n    def test_290_v6_adjust(self):\n        \"\"\"test v6_adjust()\"\"\"\n        url = \"http://192.168.123.10\"\n        self.assertEqual(\n            (\n                {\n                    \"Connection\": \"close\",\n                    \"Accept-Encoding\": \"gzip\",\n                    \"User-Agent\": \"FOOBAR\",\n                },\n                \"http://192.168.123.10\",\n            ),\n            self.v6_adjust(self.logger, url),\n        )\n\n    @patch(\"acme_srv.helpers.network.USER_AGENT\", \"FOOBAR\")\n    def test_291_v6_adjust(self):\n        \"\"\"test v6_adjust()\"\"\"\n        url = \"http://fe80::215:5dff:fec0:102\"\n        self.assertEqual(\n            (\n                {\n                    \"Connection\": \"close\",\n                    \"Accept-Encoding\": \"gzip\",\n                    \"User-Agent\": \"FOOBAR\",\n                    \"Host\": \"fe80::215:5dff:fec0:102\",\n                },\n                \"http://[fe80::215:5dff:fec0:102]/\",\n            ),\n            self.v6_adjust(self.logger, url),\n        )\n\n    def test_292_ipv6_chk(self):\n        \"\"\"test ipv6_chk()\"\"\"\n        addr_obj = \"fe80::215:5dff:fec0:102\"\n        self.assertTrue(self.ipv6_chk(self.logger, addr_obj))\n\n    def test_293_ipv6_chk(self):\n        \"\"\"test ipv6_chk()\"\"\"\n        addr_obj = \"foo.bar.local\"\n        self.assertFalse(self.ipv6_chk(self.logger, addr_obj))\n\n    def test_294_ipv6_chk(self):\n        \"\"\"test ipv6_chk()\"\"\"\n        addr_obj = \"192.168.123.10\"\n        self.assertFalse(self.ipv6_chk(self.logger, addr_obj))\n\n    def test_295_ipv6_chk(self):\n        \"\"\"test ipv6_chk()\"\"\"\n        addr_obj = None\n        self.assertFalse(self.ipv6_chk(self.logger, addr_obj))\n\n    def test_296_header_info_get(self):\n        \"\"\"header_info_get ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.return_value = (\"foo\", \"bar\")\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertEqual([\"foo\", \"bar\"], self.header_info_get(self.logger, \"csr\"))\n\n    def test_297_header_info_get(self):\n        \"\"\"header_info_get ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.side_effect = Exception(\"mock_search\")\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.header_info_get(self.logger, \"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting header_info from database: mock_search\",\n            lcm.output,\n        )\n\n    def test_298_encode_url(self):\n        # Test with a simple URL\n        url = \"www.example.com\"\n        self.assertEqual(url, self.encode_url(self.logger, url))\n\n    def test_299_encode_url(self):\n        # Test with a URL containing spaces\n        url = \"www.example.com/hello world\"\n        self.assertEqual(\n            \"www.example.com/hello%20world\", self.encode_url(self.logger, url)\n        )\n\n    def test_300_encode_url(self):\n        # Test with a URL containing special characters\n        url = \"www.example.com/hello@world?foo=bar&bar=foo\"\n        self.assertEqual(\n            \"www.example.com/hello%40world%3Ffoo%3Dbar%26bar%3Dfoo\",\n            self.encode_url(self.logger, url),\n        )\n\n    def test_301_uts_now(self):\n        \"\"\"test uts_now()\"\"\"\n        self.assertIsInstance(self.uts_now(), int)\n\n    def test_302_ip_validate(self):\n        \"\"\"test ip validate\"\"\"\n        self.assertEqual(\n            (\"1.0.0.10.in-addr.arpa\", False), self.ip_validate(self.logger, \"10.0.0.1\")\n        )\n\n    def test_303_ip_validate(self):\n        \"\"\"test ip validate\"\"\"\n        self.assertEqual((None, True), self.ip_validate(self.logger, \"1000.0.0.1\"))\n\n    def test_304_cert_ski_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDIzCCAgugAwIBAgICBZgwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAxMPZm9v\n                LmV4YW1wbGUuY29tMB4XDTE5MDEyMDE3MDkxMVoXDTE5MDIxOTE3MDkxMVowGjEY\n                MBYGA1UEAxMPZm9vLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n                MIIBCgKCAQEA+EM+gzAyjegQSRbJI+qZJhuAGM9i48xvIfuOQHleXoJPjV+8VZRV\n                KDljZNXdNT5Zi7K6HY9C622NOV7QefB6zTtm6mSY08ypNsaeorhIvJdnpaJ9gAGH\n                YeQqJ04fL099kiRXJAv8gT8wdpiekg2KEU4wlXMIRfSHiiB37yjcqUzXl6XYYKGe\n                2USMpDfliXL3o8TW2KByGUdCzXUdNbMgzRXwYxkX2+xV2f0vn8NyXHiHg9yJRof2\n                HTjyvAcXN5Nr987slq/Ex5lXLtpB861Ov3ZbwxyzREjmreZBlze7KTfP5IY66XuN\n                Mvhi7AAs0cLTd3SNjpppE/yvUi5q5gfhXQIDAQABo3MwcTAfBgNVHSMEGDAWgBSl\n                YnpKQw12MmEMpvsTEeQi17UsnDAdBgNVHQ4EFgQUpWJ6SkMNdjJhDKb7ExHkIte1\n                LJwwLwYDVR0RBCgwJoIRZm9vLTIuZXhhbXBsZS5jb22CEWZvby0xLmV4YW1wbGUu\n                Y29tMA0GCSqGSIb3DQEBCwUAA4IBAQASA20TtMPXIHH10dikLhFuI14EOtZzXvCx\n                kGlJw9/5JuvVKLsL1wd8BC9o/lg8apDqsrDZ/+0Nc8g3Z9HRN99vcLsVDdT27DkM\n                BslfXdN/qBhKAp3m7jw29uijX5fss+Wz9iHfHciUjVyMJ4DoFxHYPbMWQG8XEUKR\n                TP6Gp79DzCiPKFt52Y8yVikIET4fnyRzU8kGKLuPoIt+EQQzpG26qWAjeNHAASEM\n                keiA+tedMWzydX52B+tGg+l2svxg34apIBDjK8pF+8ZxTt5yjVUa10GbpffJuiEh\n                NWQddOR8IHg+v6lWc9BtuuKK5ubsg6XOiEjhhr42AKViKalX1i4+\"\"\"\n        self.assertEqual(\n            \"a5627a4a430d7632610ca6fb1311e422d7b52c9c\",\n            self.cert_ski_get(self.logger, cert),\n        )\n\n    def test_305_cert_ski_pyopenssl_get(self):\n        \"\"\"test cert_san_get for a multiple SAN of type DNS\"\"\"\n        cert = \"\"\"MIIDIzCCAgugAwIBAgICBZgwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAxMPZm9v\n                LmV4YW1wbGUuY29tMB4XDTE5MDEyMDE3MDkxMVoXDTE5MDIxOTE3MDkxMVowGjEY\n                MBYGA1UEAxMPZm9vLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n                MIIBCgKCAQEA+EM+gzAyjegQSRbJI+qZJhuAGM9i48xvIfuOQHleXoJPjV+8VZRV\n                KDljZNXdNT5Zi7K6HY9C622NOV7QefB6zTtm6mSY08ypNsaeorhIvJdnpaJ9gAGH\n                YeQqJ04fL099kiRXJAv8gT8wdpiekg2KEU4wlXMIRfSHiiB37yjcqUzXl6XYYKGe\n                2USMpDfliXL3o8TW2KByGUdCzXUdNbMgzRXwYxkX2+xV2f0vn8NyXHiHg9yJRof2\n                HTjyvAcXN5Nr987slq/Ex5lXLtpB861Ov3ZbwxyzREjmreZBlze7KTfP5IY66XuN\n                Mvhi7AAs0cLTd3SNjpppE/yvUi5q5gfhXQIDAQABo3MwcTAfBgNVHSMEGDAWgBSl\n                YnpKQw12MmEMpvsTEeQi17UsnDAdBgNVHQ4EFgQUpWJ6SkMNdjJhDKb7ExHkIte1\n                LJwwLwYDVR0RBCgwJoIRZm9vLTIuZXhhbXBsZS5jb22CEWZvby0xLmV4YW1wbGUu\n                Y29tMA0GCSqGSIb3DQEBCwUAA4IBAQASA20TtMPXIHH10dikLhFuI14EOtZzXvCx\n                kGlJw9/5JuvVKLsL1wd8BC9o/lg8apDqsrDZ/+0Nc8g3Z9HRN99vcLsVDdT27DkM\n                BslfXdN/qBhKAp3m7jw29uijX5fss+Wz9iHfHciUjVyMJ4DoFxHYPbMWQG8XEUKR\n                TP6Gp79DzCiPKFt52Y8yVikIET4fnyRzU8kGKLuPoIt+EQQzpG26qWAjeNHAASEM\n                keiA+tedMWzydX52B+tGg+l2svxg34apIBDjK8pF+8ZxTt5yjVUa10GbpffJuiEh\n                NWQddOR8IHg+v6lWc9BtuuKK5ubsg6XOiEjhhr42AKViKalX1i4+\"\"\"\n        self.assertEqual(\n            \"a5627a4a430d7632610ca6fb1311e422d7b52c9c\",\n            self.cert_ski_pyopenssl_get(self.logger, cert),\n        )\n\n    @patch(\"acme_srv.helpers.certificates.cert_ski_pyopenssl_get\")\n    @patch(\"acme_srv.helpers.certificates.cert_load\")\n    def test_306_ski_get(self, mock_load, mock_ski):\n        \"\"\"test cert_ski_get()\"\"\"\n        cert = \"cert\"\n        mock_ski.return_value = \"mock_ski\"\n        mock_load.return_value = \"mock_load\"\n        self.assertEqual(\"mock_ski\", self.cert_ski_get(self.logger, cert))\n        self.assertTrue(mock_ski.called)\n\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    def test_307_ski_get(self, mock_load):\n        \"\"\"test cert_ski_get()\"\"\"\n        cert = \"cert\"\n        mock_load.get_extension_count.return_value = 2\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cert_ski_pyopenssl_get(self.logger, cert))\n        self.assertIn(\n            \"WARNING:test_a2c:No SKI found in certificate\",\n            lcm.output,\n        )\n\n    def test_308_cert_aki_get(self):\n        \"\"\"test cert_san_get aki\"\"\"\n        cert = \"MIIEOzCCAiOgAwIBAgIIKndYX0qdb04wDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yNDAxMjkyMDA0NTZaFw0yNTAxMjgyMDA0NTZaMD8xFzAVBgNVBAMTDmxlZ28uYmFyLmxvY2FsMRcwFQYDVQQKDA5hY21lMmNlcnRpZmllcjELMAkGA1UEBhMCREUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQKIqEIxeS0JIN+iqsJ+08IJFFmuvfpjFnH4wFD2OLlmeTvfpDsnD00uw/orLvecDvjt48JvgYR8Wv+9C4ajIDfo4IBGTCCARUwHQYDVR0OBBYEFCka80MPgj45/quHJ9oF8Cc1YlsXMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAsGA1UdDwQEAwID6DBRBgNVHSUBAf8ERzBFBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgcrBgEFAgMFMEoGA1UdHwRDMEEwHqAcoBqGGFVSSTpodHRwOi8vZm9vLmJhci5sb2NhbDAfoB2gG4YZVVJJOmh0dHA6Ly9mb28xLmJhci5sb2NhbDAMBgNVHRMBAf8EAjAAMBkGA1UdEQQSMBCCDmxlZ28uYmFyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4ICAQB4FxJwQ/aILMzh7jBSr358RA92mX8srPmzQrjPYoU7T2LxwMf+eb0z5x0PMFH8j5FgRvRGWo6rcco8rL+B+gvrVhQ0TfAFEF77WJfKG2XMlnEN/9Ri73J7+dA45kaw8CZRSfUBpIW6fb4N+6frXyIKwBaZnrT6qiy+Izu+ZH6RkaTFrBn5yOWvVyk7aBHE1eZ+3+eA3qBI4UPaeYFSwr3gY5dxfbPktlFgvpCI22ff4NAb/fzjAQsKRTkXkOVqAvBJcWI5d/g32IVMLq0ub13XLe+yHk0iCxyMaIRdN4+W6RYi3gvtTQh6LaOjncWDYLdsm+vN+YqXEqieY5TC1oC8kG9We9eHzKHdNquJnrju536DPqh4xYEDcb+PGvTr3sqYdSikA9v5FuWUGeiZD/ZEvw/p7F7DevD5NO1JaOtfWDwDwxFHEyn+iwTVq3QDEc4j+oyGnQJs5Spoyz3tJi31VMJk+EAKKUV66aVNynLM7Ce4Oj0M67o4pcnDd0uWBMSAg4lH8KIX0IsmMfLnirIqOOwrZ4UkPKlEjD+oZQf5IBukfdHob/bo4fW8q4eU/I8z9w3BTdV1yNVH/ANHg5AItoPabkr65oBTwY51j3FVq0gK+4xVrevcyIeY3A9XFzA18k/gX7O/kf/IrM0dcZWJnsW39byiWhUd4JetJaGeKg\"\n        self.assertEqual(\n            \"bfde8e8e062222abad43c8ddd44fa8ab48413ed5\",\n            self.cert_aki_get(self.logger, cert),\n        )\n\n    def test_309_cert_aki_pyopenssl_get(self):\n        \"\"\"test cert_san_get aki\"\"\"\n        cert = \"MIIEOzCCAiOgAwIBAgIIKndYX0qdb04wDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxDzANBgNVBAMTBnN1Yi1jYTAeFw0yNDAxMjkyMDA0NTZaFw0yNTAxMjgyMDA0NTZaMD8xFzAVBgNVBAMTDmxlZ28uYmFyLmxvY2FsMRcwFQYDVQQKDA5hY21lMmNlcnRpZmllcjELMAkGA1UEBhMCREUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQKIqEIxeS0JIN+iqsJ+08IJFFmuvfpjFnH4wFD2OLlmeTvfpDsnD00uw/orLvecDvjt48JvgYR8Wv+9C4ajIDfo4IBGTCCARUwHQYDVR0OBBYEFCka80MPgj45/quHJ9oF8Cc1YlsXMB8GA1UdIwQYMBaAFL/ejo4GIiKrrUPI3dRPqKtIQT7VMAsGA1UdDwQEAwID6DBRBgNVHSUBAf8ERzBFBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgcrBgEFAgMFMEoGA1UdHwRDMEEwHqAcoBqGGFVSSTpodHRwOi8vZm9vLmJhci5sb2NhbDAfoB2gG4YZVVJJOmh0dHA6Ly9mb28xLmJhci5sb2NhbDAMBgNVHRMBAf8EAjAAMBkGA1UdEQQSMBCCDmxlZ28uYmFyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4ICAQB4FxJwQ/aILMzh7jBSr358RA92mX8srPmzQrjPYoU7T2LxwMf+eb0z5x0PMFH8j5FgRvRGWo6rcco8rL+B+gvrVhQ0TfAFEF77WJfKG2XMlnEN/9Ri73J7+dA45kaw8CZRSfUBpIW6fb4N+6frXyIKwBaZnrT6qiy+Izu+ZH6RkaTFrBn5yOWvVyk7aBHE1eZ+3+eA3qBI4UPaeYFSwr3gY5dxfbPktlFgvpCI22ff4NAb/fzjAQsKRTkXkOVqAvBJcWI5d/g32IVMLq0ub13XLe+yHk0iCxyMaIRdN4+W6RYi3gvtTQh6LaOjncWDYLdsm+vN+YqXEqieY5TC1oC8kG9We9eHzKHdNquJnrju536DPqh4xYEDcb+PGvTr3sqYdSikA9v5FuWUGeiZD/ZEvw/p7F7DevD5NO1JaOtfWDwDwxFHEyn+iwTVq3QDEc4j+oyGnQJs5Spoyz3tJi31VMJk+EAKKUV66aVNynLM7Ce4Oj0M67o4pcnDd0uWBMSAg4lH8KIX0IsmMfLnirIqOOwrZ4UkPKlEjD+oZQf5IBukfdHob/bo4fW8q4eU/I8z9w3BTdV1yNVH/ANHg5AItoPabkr65oBTwY51j3FVq0gK+4xVrevcyIeY3A9XFzA18k/gX7O/kf/IrM0dcZWJnsW39byiWhUd4JetJaGeKg\"\n        self.assertEqual(\n            \"bfde8e8e062222abad43c8ddd44fa8ab48413ed5\",\n            self.cert_aki_pyopenssl_get(self.logger, cert),\n        )\n\n    @patch(\"acme_srv.helpers.certificates.cert_aki_pyopenssl_get\")\n    @patch(\"acme_srv.helpers.certificates.cert_load\")\n    def test_310_aki_get(self, mock_load, mock_aki):\n        \"\"\"test cert_ski_get()\"\"\"\n        cert = \"cert\"\n        mock_aki.return_value = \"mock_aki\"\n        mock_load.return_value = \"mock_load\"\n        self.assertEqual(\"mock_aki\", self.cert_aki_get(self.logger, cert))\n        self.assertTrue(mock_aki.called)\n\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    def test_311_aki_get(self, mock_load):\n        \"\"\"test cert_aki_get()\"\"\"\n        cert = \"cert\"\n        mock_load.get_extension_count.return_value = 2\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cert_aki_pyopenssl_get(self.logger, cert))\n        self.assertIn(\n            \"WARNING:test_a2c:No AKI found in certificate\",\n            lcm.output,\n        )\n\n    def test_312_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertTrue(self.validate_fqdn(self.logger, \"foo.bar.com\"))\n\n    def test_313_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"-foo.bar.com\"))\n\n    def test_314_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"foo.bar.com/foo\"))\n\n    def test_315_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"foo.bar.com#foo\"))\n\n    def test_316_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"foo.bar.com?foo=foo\"))\n\n    def test_317_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(\n            self.validate_fqdn(self.logger, \"2a01:c22:b0cf:600:74be:80a7:4feb:bfe8\")\n        )\n\n    def test_318_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"foo.bar.com:8080\"))\n\n    def test_319_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertFalse(self.validate_fqdn(self.logger, \"foo@bar.local\"))\n\n    def test_320_validate_fqdn(self):\n        \"\"\"test validate_fqdn()\"\"\"\n        self.assertTrue(self.validate_fqdn(self.logger, \"*.bar.local\"))\n\n    def test_321_validate_ip(self):\n        \"\"\"test validate_ip()\"\"\"\n        self.assertTrue(self.validate_ip(self.logger, \"10.0.0.1\"))\n\n    def test_322_validate_ip(self):\n        \"\"\"test validate_ip()\"\"\"\n        self.assertTrue(\n            self.validate_ip(self.logger, \"2a01:c22:b0cf:600:74be:80a7:4feb:bfe8\")\n        )\n\n    def test_323_validate_ip(self):\n        \"\"\"test validate_ip()\"\"\"\n        self.assertFalse(self.validate_ip(self.logger, \"foo.bar.local\"))\n\n    def test_324_validate_ip(self):\n        \"\"\"test validate_ip()\"\"\"\n        self.assertFalse(self.validate_ip(self.logger, \"foo@bar.local\"))\n\n    def test_325_validate_ip(self):\n        \"\"\"test validate_ip()\"\"\"\n        self.assertFalse(self.validate_ip(self.logger, \"301.0.0.1\"))\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_326_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertEqual(\n            \"dns\", self.validate_identifier(self.logger, \"dns\", \"foo.bar.com\")\n        )\n        self.assertTrue(mock_fqdn.called)\n        self.assertFalse(mock_ip.called)\n        self.assertFalse(mock_email.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_327_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertEqual(\"ip\", self.validate_identifier(self.logger, \"ip\", \"ip\"))\n        self.assertFalse(mock_fqdn.called)\n        self.assertTrue(mock_ip.called)\n        self.assertFalse(mock_email.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_328_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertFalse(self.validate_identifier(self.logger, \"unk\", \"ip\"))\n        self.assertFalse(mock_fqdn.called)\n        self.assertFalse(mock_ip.called)\n        self.assertFalse(mock_email.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_329_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertFalse(self.validate_identifier(self.logger, \"tnauthlist\", \"ip\"))\n        self.assertFalse(mock_fqdn.called)\n        self.assertFalse(mock_ip.called)\n        self.assertFalse(mock_email.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_330_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertTrue(self.validate_identifier(self.logger, \"tnauthlist\", \"ip\", True))\n        self.assertFalse(mock_fqdn.called)\n        self.assertFalse(mock_ip.called)\n        self.assertFalse(mock_email.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_email\")\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_331_validate_identifier(self, mock_ip, mock_fqdn, mock_email):\n        \"\"\"test validate_identifier\"\"\"\n        mock_fqdn.return_value = \"dns\"\n        mock_ip.return_value = \"ip\"\n        self.assertTrue(self.validate_identifier(self.logger, \"email\", \"email\", True))\n        self.assertFalse(mock_fqdn.called)\n        self.assertFalse(mock_ip.called)\n        self.assertTrue(mock_email.called)\n\n    @patch(\"acme_srv.helpers.config.profile_lookup\")\n    @patch(\"acme_srv.helpers.config.header_info_lookup\")\n    def test_332_client_parameter_validate(self, mock_lookup, mock_profile):\n        \"\"\"test client_parameter_validate\"\"\"\n        mock_lookup.return_value = \"value2\"\n        mock_profile.return_value = \"value1\"\n        cahandler = FakeDBStore()\n        cahandler.profiles = {\"foo\": \"bar\"}\n        self.assertEqual(\n            (\"value1\", None),\n            self.client_parameter_validate(\n                self.logger, \"csr\", cahandler, \"key\", [\"value0\", \"value1\", \"value2\"]\n            ),\n        )\n        self.assertFalse(mock_lookup.called)\n        self.assertTrue(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.config.profile_lookup\")\n    @patch(\"acme_srv.helpers.config.header_info_lookup\")\n    def test_333_client_parameter_validate(self, mock_lookup, mock_profile):\n        \"\"\"test client_parameter_validate\"\"\"\n        mock_lookup.return_value = \"value2\"\n        cahandler = FakeDBStore()\n        self.assertEqual(\n            (\"value2\", None),\n            self.client_parameter_validate(\n                self.logger, \"csr\", cahandler, \"key\", [\"value0\", \"value2\"]\n            ),\n        )\n        self.assertTrue(mock_lookup.called)\n        self.assertFalse(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.config.profile_lookup\")\n    @patch(\"acme_srv.helpers.config.header_info_lookup\")\n    def test_334_client_parameter_validate(self, mock_lookup, mock_profile):\n        \"\"\"test client_parameter_validate\"\"\"\n        mock_lookup.return_value = \"unk_value\"\n        cahandler = FakeDBStore()\n        self.assertEqual(\n            (None, 'parameter \"unk_value\" is not allowed'),\n            self.client_parameter_validate(\n                self.logger,\n                \"csr\",\n                cahandler,\n                \"parameter\",\n                [\"value0\", \"value2\"],\n            ),\n        )\n        self.assertTrue(mock_lookup.called)\n        self.assertFalse(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.config.profile_lookup\")\n    @patch(\"acme_srv.helpers.config.header_info_lookup\")\n    def test_335_client_parameter_validate(self, mock_lookup, mock_profile):\n        \"\"\"test client_parameter_validate\"\"\"\n        mock_lookup.return_value = None\n        cahandler = FakeDBStore()\n        self.assertEqual(\n            (\"value0\", None),\n            self.client_parameter_validate(\n                self.logger,\n                \"csr\",\n                cahandler,\n                \"parameter\",\n                [\"value0\", \"value2\"],\n            ),\n        )\n        self.assertTrue(mock_lookup.called)\n        self.assertFalse(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_336_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = [\n            {\"header_info\": '{\"header_info_field\": \"foo1=value1 foo2=value2\"}'}\n        ]\n        self.assertEqual(\n            \"value1\",\n            self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\"),\n        )\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_337_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = [\n            {\"header_info\": '{\"header_info_field\": \"foo1=value1=foo foo2=value2=foo\"}'}\n        ]\n        self.assertEqual(\n            \"value1=foo\",\n            self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\"),\n        )\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_338_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = None\n        self.assertFalse(\n            self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\")\n        )\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_339_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = [\n            {\"foo\": '{\"header_info_field\": \"foo1=value1 foo2=value2\"}'}\n        ]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Header_info_field not found in header info: header_info_field\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_340_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = [{\"header_info\": '{\"foo\": \"foo1=value1 foo2=value2\"}'}]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Header_info_field not found in header info: header_info_field\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_341_header_info_lookup(self, mock_info):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = \"bump\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Header_info_field not found in header info: header_info_field\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.config.json.loads\")\n    @patch(\"acme_srv.helpers.network.header_info_get\")\n    def test_342_header_info_lookup(self, mock_info, mock_json):\n        \"\"\"test header_info_lookup\"\"\"\n        mock_info.return_value = [{\"header_info\": \"foo1=value1 foo2=value2\"}]\n        mock_json.side_effect = Exception(\"mock_json\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.header_info_lookup(self.logger, \"csr\", \"header_info_field\", \"foo1\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not parse header_info_field: mock_json\",\n            lcm.output,\n        )\n\n    def test_343_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\", \"bar\", \"foobar\"]'}}\n        self.assertEqual(\"foo\", self.config_headerinfo_load(self.logger, config_dic))\n\n    def test_344_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\"]'}}\n        self.assertEqual(\"foo\", self.config_headerinfo_load(self.logger, config_dic))\n\n    def test_345_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": \"foo\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.config_headerinfo_load(self.logger, config_dic))\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse header_info_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_346_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"EABhandler\"] = {\n            \"eab_profiling\": True,\n            \"eab_handler_file\": \"eab_handler_file\",\n        }\n        eabl = Mock()\n        eabl.EABhandler = \"bar\"\n        mock_eabload.return_value = eabl\n        self.assertEqual(\n            (True, \"bar\"), self.config_eab_profile_load(self.logger, config_dic)\n        )\n        self.assertTrue(mock_eabload.called)\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_347_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\"eab_profiling\": True}\n        config_dic[\"EABhandler\"] = {\"eab_handler_file\": \"eab_handler_file\"}\n        eabl = Mock()\n        eabl.EABhandler = \"bar\"\n        mock_eabload.return_value = eabl\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (True, \"bar\"), self.config_eab_profile_load(self.logger, config_dic)\n            )\n        self.assertTrue(mock_eabload.called)\n        self.assertIn(\n            \"WARNING:test_a2c:eab_profiling found in CAhandler section - this is deprecated, please use EABhandler section\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_348_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\"eab_profiling\": \"aa\"}\n        config_dic[\"EABhandler\"] = {\"eab_handler_file\": \"eab_handler_file\"}\n        eabl = Mock()\n        eabl.EABhandler = \"bar\"\n        mock_eabload.return_value = eabl\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (False, None), self.config_eab_profile_load(self.logger, config_dic)\n            )\n        self.assertFalse(mock_eabload.called)\n        self.assertIn(\n            \"WARNING:test_a2c:eab_profiling found in CAhandler section - this is deprecated, please use EABhandler section\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load eabprofile from configuration: Not a boolean: aa\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_349_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"EABhandler\"] = {\"eab_profiling\": True}\n        eabl = Mock()\n        eabl.EABhandler = \"bar\"\n        mock_eabload.return_value = eabl\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (True, None), self.config_eab_profile_load(self.logger, config_dic)\n            )\n        self.assertFalse(mock_eabload.called)\n        self.assertIn(\n            \"CRITICAL:test_a2c:EABHandler configuration incomplete\", lcm.output\n        )\n        self.assertFalse(mock_eabload.called)\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_350_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"EABhandler\"] = {\n            \"eab_profiling\": True,\n            \"eab_handler_file\": \"eab_handler_file\",\n        }\n        mock_eabload.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (True, None), self.config_eab_profile_load(self.logger, config_dic)\n            )\n        self.assertTrue(mock_eabload.called)\n        self.assertIn(\"CRITICAL:test_a2c:EABHandler could not get loaded\", lcm.output)\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_351_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"EABhandler\"] = {\n            \"eab_profiling\": False,\n            \"eab_handler_file\": \"eab_handler_file\",\n        }\n        self.assertEqual(\n            (False, None), self.config_eab_profile_load(self.logger, config_dic)\n        )\n        self.assertFalse(mock_eabload.called)\n\n    @patch(\"acme_srv.helpers.config.eab_handler_load\")\n    def test_352_config_eab_profile_load(self, mock_eabload):\n        \"\"\"test config_eab_profiling()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"EABhandler\"] = {\n            \"eab_profiling\": \"aa\",\n            \"eab_handler_file\": \"eab_handler_file\",\n        }\n        self.assertEqual(\n            (False, None), self.config_eab_profile_load(self.logger, config_dic)\n        )\n        self.assertFalse(mock_eabload.called)\n\n    def test_353_eab_profile_string_check(self):\n        \"\"\"test _eab_profile_string_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.foo = \"foo\"\n        self.eab_profile_string_check(self.logger, cahandler, \"foo\", \"bar\")\n        self.assertEqual(\"bar\", cahandler.foo)\n\n    def test_354_eab_profile_string_check(self):\n        \"\"\"test _eab_profile_string_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.foo = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.eab_profile_string_check(self.logger, cahandler, \"foobar\", \"bar\")\n        self.assertEqual(\"foo\", cahandler.foo)\n        self.assertIn(\n            \"WARNING:test_a2c:EAB profile string checking: ignoring unrecognized string attribute: key: foobar value: bar\",\n            lcm.output,\n        )\n\n    def test_355_eab_profile_list_check(self):\n        \"\"\"test _eab_profile_list_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.foo = \"foo\"\n        eabhandler = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.eab_profile_list_check(\n                self.logger, cahandler, eabhandler, \"csr\", \"foobar\", \"bar\"\n            )\n        self.assertEqual(\"foo\", cahandler.foo)\n        self.assertIn(\n            \"WARNING:test_a2c:EAP profile list checking: ignoring unrecognized list attribute: key: foobar value: bar\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    def test_356_eab_profile_list_check(self, mock_chk):\n        \"\"\"test _eab_profile_list_check()\"\"\"\n        cahandler = FakeDBStore()\n        eabhandler = Mock()\n        mock_chk.return_value = False\n        cahandler.foo = \"foo\"\n        self.eab_profile_list_check(\n            self.logger, cahandler, eabhandler, \"csr\", \"allowed_domainlist\", \"bar\"\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    def test_357_eab_profile_list_check(self, mock_chk):\n        \"\"\"test _eab_profile_list_check()\"\"\"\n        cahandler = FakeDBStore()\n        mock_chk.return_value = \"error\"\n        cahandler.foo = \"foo\"\n        eabhandler = Mock()\n        self.assertEqual(\n            \"error\",\n            self.eab_profile_list_check(\n                self.logger, cahandler, eabhandler, \"csr\", \"allowed_domainlist\", \"bar\"\n            ),\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    @patch(\"acme_srv.helpers.eab.client_parameter_validate\")\n    def test_358_eab_profile_list_check(self, mock_hifv, mock_chk):\n        \"\"\"test _eab_profile_list_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.foo = \"foo\"\n        cahandler.header_info_field = \"header_info_field\"\n        mock_chk.return_value = \"error\"\n        cahandler.foo = \"foo\"\n        eabhandler = Mock()\n        mock_hifv.return_value = (\"mock_hifv\", None)\n        self.assertFalse(\n            self.eab_profile_list_check(\n                self.logger, cahandler, eabhandler, \"csr\", \"foo\", \"bar\"\n            )\n        )\n        self.assertEqual(\"mock_hifv\", cahandler.foo)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    @patch(\"acme_srv.helpers.eab.client_parameter_validate\")\n    def test_359_eab_profile_list_check(self, mock_hifv, mock_chk):\n        \"\"\"test _eab_profile_list_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.foo = \"foo\"\n        cahandler.header_info_field = \"header_info_field\"\n        mock_chk.return_value = \"error\"\n        cahandler.foo = \"foo\"\n        eabhandler = Mock()\n        mock_hifv.return_value = (None, \"error\")\n        self.assertEqual(\n            \"error\",\n            self.eab_profile_list_check(\n                self.logger, cahandler, eabhandler, \"csr\", \"foo\", \"bar\"\n            ),\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    def test_360_eab_profile_list_check(self, mock_chk):\n        \"\"\"test _eab_profile_list_check() test allowed domain check if cahander contains attribute\"\"\"\n        cahandler = FakeDBStore()\n        mock_chk.return_value = False\n        cahandler.allowed_domainlist = [\"foo\", \"foobar\"]\n        cahandler.foo = \"foo\"\n        cahandler.header_info_field = None\n        eabhandler = Mock()\n        self.assertFalse(\n            self.eab_profile_list_check(\n                self.logger, cahandler, eabhandler, \"csr\", \"allowed_domainlist\", [\"bar\"]\n            )\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n        self.assertEqual([\"foo\", \"foobar\"], cahandler.allowed_domainlist)\n        self.assertTrue(mock_chk.called)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    def test_361_eab_profile_list_check(self, mock_chk):\n        \"\"\"test _eab_profile_list_check() test allowed domain check if eabhandler contains attribute\"\"\"\n        cahandler = FakeDBStore()\n        mock_chk.return_value = False\n        cahandler.allowed_domainlist = [\"foo\", \"foobar\"]\n        cahandler.foo = \"foo\"\n        cahandler.header_info_field = None\n        eabhandler = Mock()\n        eabhandler.allowed_domains_check.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.eab_profile_list_check(\n                    self.logger,\n                    cahandler,\n                    eabhandler,\n                    \"csr\",\n                    \"allowed_domainlist\",\n                    [\"bar\"],\n                )\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Execute allowed_domains_check() from eab handler\", lcm.output\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n        self.assertEqual([\"foo\", \"foobar\"], cahandler.allowed_domainlist)\n        self.assertFalse(mock_chk.called)\n\n    @patch(\"acme_srv.helpers.eab.allowed_domainlist_check\")\n    def test_362_eab_profile_list_check(self, mock_chk):\n        \"\"\"test _eab_profile_list_check() test allowed domain check if eabhandler contains attribute\"\"\"\n        cahandler = FakeDBStore()\n        mock_chk.return_value = False\n        cahandler.allowed_domainlist = [\"foo\", \"foobar\"]\n        cahandler.foo = \"foo\"\n        cahandler.header_info_field = None\n        eabhandler = Mock()\n        eabhandler.allowed_domains_check.return_value = \"eab_error\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"eab_error\",\n                self.eab_profile_list_check(\n                    self.logger,\n                    cahandler,\n                    eabhandler,\n                    \"csr\",\n                    \"allowed_domainlist\",\n                    [\"bar\"],\n                ),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Execute allowed_domains_check() from eab handler\", lcm.output\n        )\n        self.assertEqual(\"foo\", cahandler.foo)\n        self.assertEqual([\"foo\", \"foobar\"], cahandler.allowed_domainlist)\n        self.assertFalse(mock_chk.called)\n\n    @patch(\"acme_srv.helpers.eab.profile_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_363_eab_profile_header_info_check(\n        self, mock_lookup, mock_eab, mock_profile\n    ):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = None\n        self.assertFalse(\n            self.eab_profile_header_info_check(\n                self.logger, cahandler, \"csr\", \"handler_hifield\"\n            )\n        )\n        self.assertFalse(mock_lookup.called)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.eab.profile_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_364_eab_profile_header_info_check(\n        self, mock_lookup, mock_eab, mock_profile\n    ):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = None\n        cahandler.profiles = {\"profile\": \"profile\"}\n        mock_profile.return_value = \"profile_value\"\n        self.assertFalse(\n            self.eab_profile_header_info_check(\n                self.logger, cahandler, \"csr\", \"handler_hifield\"\n            )\n        )\n        self.assertFalse(mock_lookup.called)\n        self.assertFalse(mock_eab.called)\n        self.assertTrue(mock_profile.called)\n        self.assertEqual(\"profile_value\", cahandler.handler_hifield)\n\n    @patch(\"acme_srv.helpers.eab.profile_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_365_eab_profile_header_info_check(\n        self, mock_lookup, mock_eab, mock_profile\n    ):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = None\n        cahandler.handler_hifield = \"old_value\"\n        cahandler.profiles = {\"profile\": \"profile\"}\n        mock_profile.return_value = None\n        self.assertFalse(\n            self.eab_profile_header_info_check(\n                self.logger, cahandler, \"csr\", \"handler_hifield\"\n            )\n        )\n        self.assertFalse(mock_lookup.called)\n        self.assertFalse(mock_eab.called)\n        self.assertTrue(mock_profile.called)\n        self.assertEqual(\"old_value\", cahandler.handler_hifield)\n\n    @patch(\"acme_srv.helpers.eab.profile_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_366_eab_profile_header_info_check(\n        self, mock_lookup, mock_eab, mock_profile\n    ):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = \"hi_field\"\n        mock_lookup.return_value = \"hi_value\"\n        cahandler.hi_field = \"pre_hi_field\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.eab_profile_header_info_check(\n                    self.logger, cahandler, \"csr\", \"hi_field\"\n                )\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Received enrollment parameter: hi_field value: hi_value via headerinfo field\",\n            lcm.output,\n        )\n        self.assertEqual(\"hi_value\", cahandler.hi_field)\n        self.assertTrue(mock_lookup.called)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(mock_profile.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_367_eab_profile_header_info_check(self, mock_lookup, mock_eab):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = \"hi_field\"\n        mock_lookup.return_value = \"hi_value\"\n        cahandler.hi_field = \"pre_hi_field\"\n        cahandler.profile_name = \"profile_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.eab_profile_header_info_check(self.logger, cahandler, \"csr\")\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Received enrollment parameter: profile_name value: hi_value via headerinfo field\",\n            lcm.output,\n        )\n        self.assertEqual(\"pre_hi_field\", cahandler.hi_field)\n        self.assertEqual(\"hi_value\", cahandler.profile_name)\n        self.assertTrue(mock_lookup.called)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_368_eab_profile_header_info_check(self, mock_lookup, mock_eab):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = False\n        cahandler.header_info_field = \"hi_field\"\n        mock_lookup.return_value = None\n        cahandler.hi_field = \"pre_hi_field\"\n        cahandler.profile_name = \"profile_name\"\n        self.assertFalse(\n            self.eab_profile_header_info_check(self.logger, cahandler, \"csr\")\n        )\n        self.assertEqual(\"pre_hi_field\", cahandler.hi_field)\n        self.assertEqual(\"profile_name\", cahandler.profile_name)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_369_eab_profile_header_info_check(self, mock_lookup, mock_eab):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = True\n        cahandler.eab_handler = None\n        cahandler.header_info_field = \"hi_field\"\n        mock_lookup.return_value = \"hi_value\"\n        cahandler.hi_field = \"pre_hi_field\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"Eab_profiling enabled but no handler defined\",\n                self.eab_profile_header_info_check(\n                    self.logger, cahandler, \"csr\", \"hi_field\"\n                ),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:EAB profiling enabled but no handler defined\",\n            lcm.output,\n        )\n        self.assertEqual(\"pre_hi_field\", cahandler.hi_field)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(mock_lookup.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_check\")\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    def test_370_eab_profile_header_info_check(self, mock_lookup, mock_eab):\n        \"\"\"test eab_profile_header_info_check()\"\"\"\n        cahandler = FakeDBStore()\n        cahandler.eab_profiling = True\n        cahandler.eab_handler = \"eab_handler\"\n        cahandler.header_info_field = \"hi_field\"\n        mock_lookup.return_value = \"hi_value\"\n        mock_eab.return_value = \"mock_eab\"\n        cahandler.hi_field = \"pre_hi_field\"\n        self.assertEqual(\n            \"mock_eab\",\n            self.eab_profile_header_info_check(\n                self.logger, cahandler, \"csr\", \"hi_field\"\n            ),\n        )\n        self.assertEqual(\"pre_hi_field\", cahandler.hi_field)\n        self.assertFalse(mock_lookup.called)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_371_eab_profile_check(self, mock_string, mock_list):\n        \"\"\"test _eab_profile_check()\"\"\"\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = \"testField\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"testField\": \"stringValue\"\n        }\n        self.assertIsNone(\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            )\n        )\n        self.assertTrue(mock_string.called)\n        self.assertFalse(mock_list.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_372_eab_profile_check(self, mock_string, mock_list):\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = \"testField\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"testField\": [\"listValue\"]\n        }\n        mock_list.return_value = None\n        self.assertIsNone(\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            )\n        )\n        self.assertFalse(mock_string.called)\n        self.assertTrue(mock_list.called)\n\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_373_eab_profile_check(self, mock_string, mock_list, mock_hil):\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = \"testField\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"testField\": [\"listValue\"]\n        }\n        mock_list.return_value = \"mock_list\"\n        self.assertEqual(\n            \"mock_list\",\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            ),\n        )\n        self.assertFalse(mock_string.called)\n        self.assertTrue(mock_list.called)\n        self.assertFalse(mock_hil.called)\n\n    @patch(\"acme_srv.helpers.eab.header_info_lookup\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_374_eab_profile_check(self, mock_string, mock_list, mock_hil):\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = \"testField\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"testField1\": [\"listValue\"]\n        }\n        mock_list.return_value = \"mock_list\"\n        self.assertEqual(\n            'header_info field \"testField\" is not allowed by profile',\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            ),\n        )\n        self.assertFalse(mock_string.called)\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_hil.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_375_eab_profile_check(self, mock_string, mock_list):\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = \"testField\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"testField\": [\"listValue\"]\n        }\n        self.cahandler.eab_profile_list_check.return_value = \"eab_list_check\"\n        mock_list.return_value = None\n        self.assertEqual(\n            \"eab_list_check\",\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            ),\n        )\n        self.assertFalse(mock_string.called)\n        self.assertFalse(mock_list.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_subject_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_list_check\")\n    @patch(\"acme_srv.helpers.eab.eab_profile_string_check\")\n    def test_376_eab_profile_check(self, mock_string, mock_list, mock_subject):\n        self.cahandler = MagicMock()\n        self.csr = \"testCSR\"\n        self.handler_hifield = None\n        mock_subject.return_value = \"mock_subject\"\n        self.cahandler.eab_handler.return_value.__enter__.return_value.eab_profile_get.return_value = {\n            \"subject\": [\"listValue\"]\n        }\n        self.cahandler.eab_profile_list_check.return_value = \"eab_list_check\"\n        mock_list.return_value = None\n        self.assertEqual(\n            \"mock_subject\",\n            self.eab_profile_check(\n                self.logger, self.cahandler, self.csr, self.handler_hifield\n            ),\n        )\n        self.assertFalse(mock_string.called)\n        self.assertFalse(mock_list.called)\n        self.assertTrue(mock_subject.called)\n\n    @patch(\"cryptography.__version__\", \"3.4.7\")\n    def test_377_cryptography_version_get_success(self):\n        self.assertEqual(3, self.cryptography_version_get(self.logger))\n\n    @patch(\"cryptography.__version__\", None)\n    def test_378_cryptography_version_get_success(self):\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(36, self.cryptography_version_get(self.logger))\n        self.assertIn(\n            \"ERROR:test_a2c:Error while getting the version number of the cryptography module: 'NoneType' object has no attribute 'split'\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_379_cn_validate(self, mock_ip, mock_fqdn):\n        \"\"\"test cn_validate()\"\"\"\n        mock_ip.return_value = True\n        mock_fqdn.return_value = True\n        self.assertFalse(self.cn_validate(self.logger, \"foo.bar.com\"))\n        self.assertFalse(mock_fqdn.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_380_cn_validate(self, mock_ip, mock_fqdn):\n        \"\"\"test cn_validate()\"\"\"\n        mock_ip.return_value = False\n        mock_fqdn.return_value = True\n        self.assertFalse(self.cn_validate(self.logger, \"foo.bar.com\"))\n        self.assertTrue(mock_fqdn.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_381_cn_validate(self, mock_ip, mock_fqdn):\n        \"\"\"test cn_validate()\"\"\"\n        mock_ip.return_value = False\n        mock_fqdn.return_value = False\n        self.assertEqual(\n            \"Profile subject check failed: CN validation failed\",\n            self.cn_validate(self.logger, \"foo.bar.com\"),\n        )\n        self.assertTrue(mock_fqdn.called)\n\n    @patch(\"acme_srv.helpers.validation.validate_fqdn\")\n    @patch(\"acme_srv.helpers.validation.validate_ip\")\n    def test_382_cn_validate(self, mock_ip, mock_fqdn):\n        \"\"\"test cn_validate()\"\"\"\n        mock_ip.return_value = False\n        mock_fqdn.return_value = False\n        self.assertEqual(\n            \"Profile subject check failed: commonName missing\",\n            self.cn_validate(self.logger, None),\n        )\n        self.assertFalse(mock_fqdn.called)\n\n    def test_383_csr_subject_get(self):\n        \"\"\"test csr_subject_get()\"\"\"\n        csr = \"MIICwDCCAagCAQAwVDESMBAGA1UEAwwJbGVnby5hY21lMQ0wCwYDVQQKDARhY21lMQwwCgYDVQQLDANmb28xCzAJBgNVBAYTAlVTMRQwEgYDVQQFEwswMC0xMS0yMi0zMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5AKMmB3o8LLEEGuHo0Ipl4K8z9m3EyM9teSVocQz39DK8s2dKpx8MrsVkTg6M3fuL4yPlim8v0+unPtB18dFeThkijHetxL5x08pVvMVwa7Cjk/22e5IRgBGSQYCO6KCUsNh2vhH93r7x71wlTV3sYe2t0HaEdGqBxdct76J9kyeCY06Br+4PMR7afRvHv4vFH6Y2+hSD4oOd5cSTZXnNWcWRbjNFY7aytzl4JpJiEK0ealDMSf/ZP0n8Sdx1vCx8amaozrLg5z3eLULiAUUgCtqOWOgNLQFNSqjyhZmMTZGGJcTgb43KAKWsO3bfM6rvNTZRbrM7dAsg/bQsK6mMCAwEAAaAnMCUGCSqGSIb3DQEJDjEYMBYwFAYDVR0RBA0wC4IJbGVnby5hY21lMA0GCSqGSIb3DQEBCwUAA4IBAQA19j8Lge9Vqxc/hvWYcU1Kx3KBx5TN97PK0wQFPIIWX20/JRoodzfrMSqO0EgZWB+czoRi8G+2ezbK13sV02dKovo8ISoSvgSZtt53UKBz+JmQd7Q7G1vONZ7d2PT0nTUN4fTA5YQs5nys3O8/2oOxJiJO6IyhmpiVqUbrlU6Harb4MfjNTb+teSQRSCOAX/8U9TdPwuAi6rXdWjXAUxBDQySWkW/B3pd77Ztt5nDFP2DT+7f7mAoWG4+XY6iXcXs1GsDA4XRTx2rCvhQtQomVGAKFwd8aTpHL/ZwNt1GOw6oMZkKKf+axVA1pvAYGhey/4x3uwKf654VB3e2iOCea\"\n        result_dic = {\n            \"commonName\": \"lego.acme\",\n            \"organizationName\": \"acme\",\n            \"organizationalUnitName\": \"foo\",\n            \"countryName\": \"US\",\n            \"serialNumber\": \"00-11-22-33\",\n        }\n        self.assertEqual(result_dic, self.csr_subject_get(self.logger, csr))\n\n    def test_384_csr_subject_get(self):\n        \"\"\"test csr_subject_get()\"\"\"\n        csr = \"MIICtjCCAZ4CAQAwQjELMAkGA1UEBhMCREUxDjAMBgNVBAoMBXRlc3RPMQ8wDQYDVQQLDAZ0ZXN0T1UxEjAQBgNVBGEMCTEyMzQ1NjctODCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs0a3qYQOIkpuRa34QEb+j9PzkMC8eA8bT9icRnpd1DO5LyQjc0OreV8ed3YeV1IVtcf1qX4AYKdIb1X1qa1pFkcneFZsX6B1i/ofRqEXrsN243V4LTjHFwIqwIecFX/Ml9rhCV+/tRTBrl3XIyI2xhZ4qtxIWkavmrvhNy4gY0YBjw4D67NzDJ9gm9Nx8VFzGZxXP0MgOtLOJ7BMCcqJmBwdItaotFCCkQfXC6S+n9sLP3GYrgyShaXMAebYmkNPZ4YJ0H28VfBGcWF2hpmSBgZ15Bj5P3PNYAnAocCUkwcifk3CZoJwcmC1Fm2mC8zOAEQ+GA/KqM6RHmMMKHexECAwEAAaAvMC0GCSqGSIb3DQEJDjEgMB4wHAYDVR0RBBUwE4IRYWNtZS1jbGllbnQubG9jYWwwDQYJKoZIhvcNAQELBQADggEBAB6HE9CMFKvyM4kwmKKeAoXzLhILTWmjDgI1+wBEq781CqXS3/rhTRYxFCjaU4WUFSHFUOo4+qlehwQzFRBLwEdgIylKXVT7etuto9lHU7xpf+wRth3c/PF/DsibC3S+fzlA7UBgxvhO0FbuqnV4pUUp/Y/jsaB4+IWhnwySh+378A98VkLU/1muSaM5AS9rboYyFPtevWzeSZJtz7CLAK2zWJ95ApOIBXHQdm6wsgJzwTTW1apXofNTX5AM6L0TPdieiPKUHPpH2AJZjzCVX9NuhDhLL5klzYwIrvcD2bxGy+xNWAYxXyLhPkGG8F954FAFa66sqiQBlmU92ndtG3Q=\"\n        result_dic = {\n            \"organizationIdentifier\": \"1234567-8\",\n            \"organizationName\": \"testO\",\n            \"organizationalUnitName\": \"testOU\",\n            \"countryName\": \"DE\",\n        }\n        self.assertEqual(result_dic, self.csr_subject_get(self.logger, csr))\n\n    def test_385_csr_subject_get(self):\n        \"\"\"test csr_subject_get()\"\"\"\n        csr = \"MIICcDCCAVgCAQAwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOKk0E61QJ2K/NiGSO0aJyqrLfmHytPr35ptLwNdfKQ/8Vb2uoHYAvxVEO9weNTQVlZ9ApkJquBTRoSdqTy6p87inh8JwzFM/neJAsMg2ZiH3gRRRfmIb/4Kce0BUQ66DFSV8sWThyv13EcL+pZYdqRvONujVn7XVPbmB2ZI8qI4iXswRq45mFBW5Dyt3Rlw+KOBu1ejo0lqB2FGQiBONxQrFDyF4nVWN3R9BlhuybSF4Elhos7pkiEfrE+8EzYy+7yMEiDh1m+TmwZRNEdtSWNORF51CF3bYUz8pvpt66vKGi/F6k2iljelw1kNsswZAciNi2jG7S0M+MWMFi680sCAwEAAaArMCkGCSqGSIb3DQEJDjEcMBowGAYDVR0RBBEwD4INZm9vLmJhci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAivCrcL+uVzDdykT87073atC4B2DHky5bzL+iI8C+BkPq0jRdcVkExMrUtTdtp8Ot1zQHtYc/c/Tj+aYDZ6SdMYtrtHUgxS5JyFh0p+MEvkgZHcWOVC+VlWA+lC9kdX3WetsGT6xqCG4l+BpgCUERghFJ5/+K0bbCI4jT/5ZCT7+pO0qZtw0eg6tQBLPSXzXN98x3nmuaw9PzO1rVG5IMItyU+TlX3pJRXKpqSOHEbeaGWHizMUlbDKzoIiUf+11I9RwTeLlp/HPG8uvRc/zZ1einZPLQgow5kU15jFQSgQtzFHV4ZxuYmWN7oMIruwBNP1hkoTNL1kJcPeOwtEdOMw==\"\n        self.assertFalse(self.csr_subject_get(self.logger, csr))\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_386_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": \"bar1\"}\n        self.assertEqual(\n            \"Profile subject check failed for foo\",\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"foo\", \"bar\"\n            ),\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_387_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": \"*\"}\n        self.assertFalse(\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"foo\", \"bar\"\n            )\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_388_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": \"bar\"}\n        self.assertFalse(\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"foo\", \"bar\"\n            )\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_389_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": [\"bar1\", \"bar2\", \"bar3\"]}\n        self.assertEqual(\n            \"Profile subject check failed for foo\",\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"foo\", \"bar\"\n            ),\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_390_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": [\"bar1\", \"bar2\", \"bar3\"]}\n        self.assertFalse(\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"foo\", \"bar2\"\n            )\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_391_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": [\"bar1\", \"bar2\", \"bar3\"]}\n        mock_validate.return_value = \"error\"\n        self.assertEqual(\n            \"error\",\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"commonName\", \"bar\"\n            ),\n        )\n        self.assertTrue(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.cn_validate\")\n    def test_392_eab_profile_subjet_string_check(self, mock_validate):\n        \"\"\"test eab_profile_subject_string_check()\"\"\"\n        profile_dic = {\"foo\": [\"bar1\", \"bar2\", \"bar3\"]}\n        mock_validate.return_value = \"error\"\n        self.assertEqual(\n            \"Profile subject check failed for bar\",\n            self.eab_profile_subject_string_check(\n                self.logger, profile_dic, \"bar\", \"bar\"\n            ),\n        )\n        self.assertFalse(mock_validate.called)\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_subject_string_check\")\n    @patch(\"acme_srv.helpers.eab.csr_subject_get\")\n    def test_393_eab_profile_subject_check(self, mock_cn, mock_strchk):\n        \"\"\"test eab_profile_subject_check()\"\"\"\n        profile_dic = {\"foo\": \"bar\"}\n        mock_cn.return_value = {\"o\": \"o\", \"ou\": \"ou\", \"cn\": \"cn\"}\n        mock_strchk.side_effect = [\"o\", \"ou\", \"cn\"]\n        self.assertEqual(\n            \"o\", self.eab_profile_subject_check(self.logger, \"csr\", profile_dic)\n        )\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_subject_string_check\")\n    @patch(\"acme_srv.helpers.eab.csr_subject_get\")\n    def test_394_eab_profile_subject_check(self, mock_cn, mock_strchk):\n        \"\"\"test eab_profile_subject_check()\"\"\"\n        profile_dic = {\"foo\": \"bar\"}\n        mock_cn.return_value = {\"o\": \"o\", \"ou\": \"ou\", \"cn\": \"cn\"}\n        mock_strchk.side_effect = [False, \"ou\", \"cn\"]\n        self.assertEqual(\n            \"ou\", self.eab_profile_subject_check(self.logger, \"csr\", profile_dic)\n        )\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_subject_string_check\")\n    @patch(\"acme_srv.helpers.eab.csr_subject_get\")\n    def test_395_eab_profile_subject_check(self, mock_cn, mock_strchk):\n        \"\"\"test eab_profile_subject_check()\"\"\"\n        profile_dic = {\"foo\": \"bar\"}\n        mock_cn.return_value = {\"o\": \"o\", \"ou\": \"ou\", \"cn\": \"cn\"}\n        mock_strchk.side_effect = [False, False, \"cn\"]\n        self.assertEqual(\n            \"cn\", self.eab_profile_subject_check(self.logger, \"csr\", profile_dic)\n        )\n\n    @patch(\"acme_srv.helpers.eab.eab_profile_subject_string_check\")\n    @patch(\"acme_srv.helpers.eab.csr_subject_get\")\n    def test_396_eab_profile_subject_check(self, mock_cn, mock_strchk):\n        \"\"\"test eab_profile_subject_check()\"\"\"\n        profile_dic = {\"foo\": \"bar\"}\n        mock_cn.return_value = {\"o\": \"o\", \"ou\": \"ou\", \"cn\": \"cn\"}\n        mock_strchk.side_effect = [False, False, False]\n        self.assertEqual(\n            \"Profile subject check failed\",\n            self.eab_profile_subject_check(self.logger, \"csr\", profile_dic),\n        )\n\n    @patch(\"acme_srv.helpers.csr.csr_san_get\")\n    @patch(\"acme_srv.helpers.csr.csr_cn_get\")\n    def test_397_csr_cn_lookup(self, mock_cnget, mock_san_get):\n        \"\"\"test _csr_cn_lookup()\"\"\"\n        mock_cnget.return_value = \"cn\"\n        mock_san_get.return_value = [\"foo:san1\", \"foo:san2\"]\n        self.assertEqual(\"cn\", self.csr_cn_lookup(self.logger, \"csr\"))\n\n    @patch(\"acme_srv.helpers.csr.csr_san_get\")\n    @patch(\"acme_srv.helpers.csr.csr_cn_get\")\n    def test_398_csr_cn_lookup(self, mock_cnget, mock_san_get):\n        \"\"\"test _csr_cn_lookup()\"\"\"\n        mock_cnget.return_value = None\n        mock_san_get.return_value = [\"foo:san1\", \"foo:san2\"]\n        self.assertEqual(\"san1\", self.csr_cn_lookup(self.logger, \"csr\"))\n\n    @patch(\"acme_srv.helpers.csr.csr_san_get\")\n    @patch(\"acme_srv.helpers.csr.csr_cn_get\")\n    def test_399_csr_cn_lookup(self, mock_cnget, mock_san_get):\n        \"\"\"test _csr_cn_lookup()\"\"\"\n        mock_cnget.return_value = None\n        mock_san_get.return_value = [\"foosan1\", \"foo:san2\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"san2\", self.csr_cn_lookup(self.logger, \"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:SAN split failed: list index out of range\", lcm.output\n        )\n\n    @patch(\"acme_srv.helpers.csr.csr_san_get\")\n    @patch(\"acme_srv.helpers.csr.csr_cn_get\")\n    def test_400_csr_cn_lookup(self, mock_cnget, mock_san_get):\n        \"\"\"test _csr_cn_lookup()\"\"\"\n        mock_cnget.return_value = None\n        mock_san_get.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.csr_cn_lookup(self.logger, \"csr\"))\n        self.assertIn(\"ERROR:test_a2c:No SANs found in CSR\", lcm.output)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_401_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = lambda: {\"get\": \"get\"}\n        mock_get.return_value = mockresponse_get\n        mockresponse_post = Mock()\n        mockresponse_post.status_code = \"status_code\"\n        mockresponse_post.json = lambda: {\"post\": \"post\"}\n        mock_post.return_value = mockresponse_post\n        mockresponse_put = Mock()\n        mockresponse_put.status_code = \"status_code\"\n        mockresponse_put.json = lambda: {\"put\": \"put\"}\n        mock_put.return_value = mockresponse_put\n        self.assertEqual(\n            (\"status_code\", {\"get\": \"get\"}),\n            self.request_operation(logger=self.logger, url=\"foo\", method=\"get\"),\n        )\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_put.called)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_402_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = lambda: {\"get\": \"get\"}\n        mock_get.return_value = mockresponse_get\n        mockresponse_post = Mock()\n        mockresponse_post.status_code = \"status_code\"\n        mockresponse_post.json = lambda: {\"post\": \"post\"}\n        mock_post.return_value = mockresponse_post\n        mockresponse_put = Mock()\n        mockresponse_put.status_code = \"status_code\"\n        mockresponse_put.json = lambda: {\"put\": \"put\"}\n        mock_put.return_value = mockresponse_put\n        self.assertEqual(\n            (\"status_code\", {\"post\": \"post\"}),\n            self.request_operation(logger=self.logger, url=\"foo\", method=\"post\"),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_put.called)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_403_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = lambda: {\"get\": \"get\"}\n        mock_get.return_value = mockresponse_get\n        mockresponse_post = Mock()\n        mockresponse_post.status_code = \"status_code\"\n        mockresponse_post.json = lambda: {\"post\": \"post\"}\n        mock_post.return_value = mockresponse_post\n        mockresponse_put = Mock()\n        mockresponse_put.status_code = \"status_code\"\n        mockresponse_put.json = lambda: {\"put\": \"put\"}\n        mock_put.return_value = mockresponse_put\n        self.assertEqual(\n            (\"status_code\", {\"put\": \"put\"}),\n            self.request_operation(logger=self.logger, url=\"foo\", method=\"put\"),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_put.called)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_404_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = \"string\"\n        mock_get.return_value = mockresponse_get\n        self.assertEqual(\n            (\"status_code\", \"'str' object is not callable\"),\n            self.request_operation(logger=self.logger, url=\"foo\", method=\"get\"),\n        )\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_put.called)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_405_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = \"string\"\n        mockresponse_get.text = None\n        mock_get.return_value = mockresponse_get\n        self.assertEqual(\n            (\"status_code\", None),\n            self.request_operation(logger=self.logger, url=\"foo\", method=\"get\"),\n        )\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_put.called)\n\n    @patch(\"acme_srv.helpers.network.requests.put\")\n    @patch(\"acme_srv.helpers.network.requests.post\")\n    @patch(\"acme_srv.helpers.network.requests.get\")\n    def test_406_request_operation(self, mock_get, mock_post, mock_put):\n        \"\"\"test request_operation()\"\"\"\n        mockresponse_get = Mock()\n        mockresponse_get.status_code = \"status_code\"\n        mockresponse_get.json = \"string\"\n        mockresponse_get.text = None\n        mock_get.return_value = mockresponse_get\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"'NoneType' object has no attribute 'status_code'\"),\n                self.request_operation(logger=self.logger, url=\"foo\", method=\"unknown\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: 'NoneType' object has no attribute 'status_code'\",\n            lcm.output,\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_put.called)\n\n    def test_407_enrollment_config_log(self):\n        \"\"\"test enrollment_config_log()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo_val\"\n        myclass.bar = \"bar_val\"\n        myclass.password = \"password_val\"\n        myclass.secret = \"secret_val\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.enrollment_config_log(self.logger, myclass))\n        self.assertIn(\n            \"INFO:test_a2c:Enrollment configuration: ['foo: foo_val', 'bar: bar_val']\",\n            lcm.output,\n        )\n\n    def test_408_enrollment_config_log(self):\n        \"\"\"test enrollment_config_log()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo_val\"\n        myclass.bar = \"bar_val\"\n        myclass.foobar = \"foobar_val\"\n        myclass.password = \"password_val\"\n        myclass.secret = \"secret_val\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.enrollment_config_log(self.logger, myclass, [\"foo\", \"bar\"])\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Enrollment configuration: ['foobar: foobar_val']\", lcm.output\n        )\n\n    def test_409_enrollment_config_log(self):\n        \"\"\"test enrollment_config_log()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo_val\"\n        myclass.bar = \"bar_val\"\n        myclass.foobar = \"foobar_val\"\n        myclass.password = \"password_val\"\n        myclass.secret = \"secret_val\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.enrollment_config_log(self.logger, myclass, \"failed to parse\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Enrollment configuration won't get logged due to a configuration error.\",\n            lcm.output,\n        )\n\n    def test_410_config_enroll_config_log_load(self):\n        \"\"\"test config_enroll_config_log_load()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\"enrollment_config_log\": \"True\"}\n        self.assertEqual(\n            (True, []), self.config_enroll_config_log_load(self.logger, config_dic)\n        )\n\n    def test_411_config_enroll_config_log_load(self):\n        \"\"\"test config_enroll_config_log_load()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\"enrollment_config_log\": \"False\"}\n        self.assertEqual(\n            (False, []), self.config_enroll_config_log_load(self.logger, config_dic)\n        )\n\n    def test_412_config_enroll_config_log_load(self):\n        \"\"\"test config_enroll_config_log_load()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\"enrollment_config_log\": \"aaa\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (False, []), self.config_enroll_config_log_load(self.logger, config_dic)\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load enrollment_config_log from configuration: Not a boolean: aaa\",\n            lcm.output,\n        )\n\n    def test_413_config_enroll_config_log_load(self):\n        \"\"\"test config_enroll_config_log_load()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\n            \"enrollment_config_log\": \"True\",\n            \"enrollment_config_log_skip_list\": '[\"foo\", \"bar\"]',\n        }\n        self.assertEqual(\n            (True, [\"foo\", \"bar\"]),\n            self.config_enroll_config_log_load(self.logger, config_dic),\n        )\n\n    def test_414_config_enroll_config_log_load(self):\n        \"\"\"test config_enroll_config_log_load()\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"CAhandler\"] = {\n            \"enrollment_config_log\": \"True\",\n            \"enrollment_config_log_skip_list\": '\"foo\",',\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (True, \"failed to parse\"),\n                self.config_enroll_config_log_load(self.logger, config_dic),\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse enrollment_config_log_skip_list from configuration: Extra data: line 1 column 6 (char 5)\",\n            lcm.output,\n        )\n\n    def test_415_config_allowed_domainlist_load(self):\n        \"\"\"test config_allowed_domainlist_load()\"\"\"\n        config_dic = {\"CAhandler\": {\"allowed_domainlist\": '[\"foo\", \"bar\", \"foobar\"]'}}\n        self.assertEqual(\n            [\"foo\", \"bar\", \"foobar\"],\n            self.config_allowed_domainlist_load(self.logger, config_dic),\n        )\n\n    def test_416_config_allowed_domainlist_load(self):\n        \"\"\"test config_allowed_domainlist_load()\"\"\"\n        config_dic = {\"CAhandler\": {\"allowed_domainlist\": '[\"foo\"]'}}\n        self.assertEqual(\n            [\"foo\"], self.config_allowed_domainlist_load(self.logger, config_dic)\n        )\n\n    def test_417_config_allowed_domainlist_load(self):\n        \"\"\"test config_allowed_domainlist_load()\"\"\"\n        config_dic = {\"CAhandler\": {\"allowed_domainlist\": \"foo\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"failed to parse\",\n                self.config_allowed_domainlist_load(self.logger, config_dic),\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load allowed_domainlist from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_418_domainlist_check(self):\n        \"\"\"domainlist_check failed check as empty entry\"\"\"\n        list_ = [\"bar.foo\", \"foo.bar\"]\n        entry = None\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_419_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted failed check as empty entry\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = None\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_420_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted check against empty list\"\"\"\n        list_ = []\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_421_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted successful check against 1st element of a list\"\"\"\n        list_ = [\"*.bar.foo\", \"*.foo.bar\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_422_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted unsuccessful as endcheck failed\"\"\"\n        list_ = [\"bar.foo\", \"foo.bar\"]\n        entry = \"host.bar.foo.bar1\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_423_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted wildcard check\"\"\"\n        list_ = [\"*.bar.foo\", \"foo.bar\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_424_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted failed wildcard check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"*.bar.foo_\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_425_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted not end check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo gna\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_426_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted $ at the end\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo$\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_427_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted unsuccessful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"\\*.bar.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_428_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted successful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"*.bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_429_is_domain_whitelisted(self):\n        \"\"\"is_domain_whitelisted successful whildcard in list but not in string\"\"\"\n        list_ = [\"foo.bar$\", \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_430_is_domain_whitelisted(self):\n        \"\"\"ip address check NOne in whitelist\"\"\"\n        list_ = [None, \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n        self.assertIn(\n            \"ERROR:test_a2c:Invalid pattern configured in allowed_domainlist: empty string\",\n            lcm.output,\n        )\n\n    @patch(\"idna.encode\")\n    def test_431_is_domain_whitelisted(self, mock_idna):\n        \"\"\"exception\"\"\"\n        list_ = [\"example.com\", \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        mock_idna.side_effect = Exception(\"idna error\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n        self.assertIn(\n            \"ERROR:test_a2c:Invalid domain format in csr: idna error\", lcm.output\n        )\n\n    def test_432_is_domain_whitelisted(self):\n        \"\"\"whitelist\"\"\"\n        list_ = [\"example.com\", \"bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_433_is_domain_whitelisted(self):\n        \"\"\"exact domain name\"\"\"\n        list_ = [\"example.com\", \"bar.foo\"]\n        entry = \"bar.foo\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    def test_434_is_domain_whitelisted(self):\n        \"\"\"wildcard domain name\"\"\"\n        list_ = [\"*.example.com\", \"*.bar.foo\"]\n        entry = \"*.example.com\"\n        self.assertTrue(self.is_domain_whitelisted(self.logger, entry, list_))\n\n    @patch(\"idna.encode\")\n    def test_435_is_domain_whitelisted(self, mock_idna):\n        \"\"\"exception\"\"\"\n        list_ = [\"example.com\", \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        mock_idna.side_effect = [Exception(\"idna error\"), \"bar\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.is_domain_whitelisted(self.logger, entry, list_))\n        self.assertIn(\n            \"ERROR:test_a2c:Invalid pattern configured in allowed_domainlist: *.bar.foo\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.helpers.domain_utils.csr_cn_get\")\n    @patch(\"acme_srv.helpers.domain_utils.csr_san_get\")\n    def test_436_allowed_domainlist_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with empty allowed_domainlist\"\"\"\n        allowed_domainlist = []\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertFalse(\n            self.allowed_domainlist_check(self.logger, csr, allowed_domainlist)\n        )\n\n    @patch(\"acme_srv.helpers.domain_utils.csr_cn_get\")\n    @patch(\"acme_srv.helpers.domain_utils.csr_san_get\")\n    def test_437_allowed_domainlist_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with empty allowed_domainlist\"\"\"\n        allowed_domainlist = [\"*.foo.bar\"]\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertFalse(\n            self.allowed_domainlist_check(self.logger, csr, allowed_domainlist)\n        )\n\n    @patch(\"acme_srv.helpers.domain_utils.csr_cn_get\")\n    @patch(\"acme_srv.helpers.domain_utils.csr_san_get\")\n    def test_438_allowed_domainlist_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with allowd allowed_domainlist\"\"\"\n        allowed_domainlist = [\"*.bar.bar\"]\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertEqual(\n            \"Either CN or SANs are not allowed by configuration\",\n            self.allowed_domainlist_check(self.logger, csr, allowed_domainlist),\n        )\n\n    @patch(\"acme_srv.helpers.domain_utils.csr_cn_get\")\n    @patch(\"acme_srv.helpers.domain_utils.csr_san_get\")\n    def test_439_allowed_domainlist_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with allowed allowed_domainlist\"\"\"\n        allowed_domainlist = [\"*.foo.bar\"]\n        mock_san.return_value = [\"invalidhostname\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertEqual(\n            \"SAN list parsing failed ['invalidhostname']\",\n            self.allowed_domainlist_check(self.logger, csr, allowed_domainlist),\n        )\n\n    @patch(\"acme_srv.helpers.domain_utils.csr_cn_get\")\n    @patch(\"acme_srv.helpers.domain_utils.csr_san_get\")\n    def test_440_allowed_domainlist_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with empty allowed_domainlist\"\"\"\n        allowed_domainlist = [\"*.foo.bar\"]\n        mock_san.return_value = [\"email:user@bar.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertFalse(\n            self.allowed_domainlist_check(self.logger, csr, allowed_domainlist)\n        )\n\n    @patch(\"random.randint\")\n    def test_441_radomize_parameter_list(self, mock_rand):\n        \"\"\"test radomize_parameter_list()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo1, foo2, foo2\"\n        myclass.bar = \"bar1, bar2, bar3\"\n        self.radomize_parameter_list(self.logger, myclass, [\"foo\", \"bar\"])\n        self.assertEqual(\"foo2\", myclass.foo)\n        self.assertEqual(\"bar2\", myclass.bar)\n\n    @patch(\"random.randint\")\n    def test_442_radomize_parameter_list(self, mock_rand):\n        \"\"\"test radomize_parameter_list()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo1, foo2, foo2\"\n        myclass.bar = \"bar1, bar2, bar3\"\n        self.radomize_parameter_list(self.logger, myclass, [\"foo1\", \"bar\"])\n        self.assertEqual(\"foo1, foo2, foo2\", myclass.foo)\n        self.assertEqual(\"bar2\", myclass.bar)\n\n    @patch(\"random.randint\")\n    def test_443_radomize_parameter_list(self, mock_rand):\n        \"\"\"test radomize_parameter_list()\"\"\"\n\n        class myclass:\n            pass\n\n        myclass.foo = \"foo1\"\n        myclass.bar = \"bar1\"\n        self.radomize_parameter_list(self.logger, myclass, [\"foo1\", \"bar\"])\n        self.assertEqual(\"foo1\", myclass.foo)\n        self.assertEqual(\"bar1\", myclass.bar)\n\n    def test_444_config_profile_load(self):\n        \"\"\"test _config_load with unknown values config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Order\"] = {\"profiles\": '{\"foo\": \"bar\", \"bar\": \"foo\"}'}\n        self.assertEqual(\n            {\"foo\": \"bar\", \"bar\": \"foo\"}, self.config_profile_load(self.logger, parser)\n        )\n\n    def test_445_config_profile_load(self):\n        \"\"\"test _config_load with unknown values config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Order\"] = {\"profiles\": \"foo\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.config_profile_load(self.logger, parser))\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load profiles from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_446_profile_lookup(self):\n        \"\"\"profile_lookup ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.return_value = [\n            {\"foo\": \"bar\", \"order__profile\": \"order_profile\"}\n        ]\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertEqual(\"order_profile\", self.profile_lookup(self.logger, \"csr\"))\n\n    def test_447_profile_lookup(self):\n        \"\"\"profile_lookup ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.return_value = None\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.profile_lookup(self.logger, \"csr\"))\n\n    def test_448_profile_lookup(self):\n        \"\"\"profile_lookup ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.return_value = [{\"foo\": \"bar\"}]\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        self.assertFalse(self.profile_lookup(self.logger, \"csr\"))\n\n    def test_449_profile_lookup(self):\n        \"\"\"profile_lookup ()\"\"\"\n        models_mock = MagicMock()\n        models_mock.DBstore().certificates_search.side_effect = Exception(\"mock_search\")\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.profile_lookup(self.logger, \"csr\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Profile lookup failed with: mock_search\",\n            lcm.output,\n        )\n\n    def test_450_b64_url_decode(self):\n        \"\"\"test b64_url_decode()\"\"\"\n        self.assertEqual(\"foo\", self.b64_url_decode(self.logger, \"Zm9v\"))\n\n    def test_451_b64_url_decode(self):\n        \"\"\"test b64_url_decode()\"\"\"\n        self.assertEqual(\n            \"thisisateststring\",\n            self.b64_url_decode(self.logger, \"dGhpc2lzYXRlc3RzdHJpbmc\"),\n        )\n\n    def test_452_b64_url_decode(self):\n        \"\"\"test b64_url_decode()\"\"\"\n        self.assertEqual(\n            \"thisisateststring\",\n            self.b64_url_decode(self.logger, \"dGhpc2lzYXRlc3RzdHJpbmc=\"),\n        )\n\n    def test_453_b64_url_decode(self):\n        \"\"\"test b64_url_decode()\"\"\"\n        self.assertEqual(\n            \"thisisateststring\",\n            self.b64_url_decode(self.logger, \"dGhpc2lzYXRlc3RzdHJpbmc    \"),\n        )\n\n    @patch(\"acme_srv.helpers.encoding.b64_url_recode\", return_value=\"encoded_cert\")\n    def test_454_eab_profile_revocation_check_str_value(self, mock_b64_url_recode):\n        \"\"\"eab_profile_dic with a string value\"\"\"\n        self.cahandler = MagicMock()\n        self.cahandler.eab_handler.return_value.__enter__.return_value = MagicMock()\n        self.certificate_raw = \"dummy_cert\"\n        eab_handler = self.cahandler.eab_handler.return_value.__enter__.return_value\n        eab_handler.eab_profile_get.return_value = {\"profile\": \"value\"}\n        with patch(\n            \"acme_srv.helpers.eab.eab_profile_string_check\"\n        ) as mock_string_check:\n            self.eab_profile_revocation_check(\n                self.logger, self.cahandler, self.certificate_raw\n            )\n            mock_string_check.assert_called_once_with(\n                self.logger, self.cahandler, \"profile\", \"value\"\n            )\n            self.assertFalse(mock_b64_url_recode.called)\n\n    @patch(\"acme_srv.helpers.encoding.b64_url_recode\", return_value=\"encoded_cert\")\n    def test_455_eab_profile_revocation_check_str_and_ignore_value(\n        self, mock_b64_url_recode\n    ):\n        \"\"\"eab_profile_dic with a string value\"\"\"\n        self.cahandler = MagicMock()\n        self.cahandler.eab_handler.return_value.__enter__.return_value = MagicMock()\n        self.certificate_raw = \"dummy_cert\"\n        eab_handler = self.cahandler.eab_handler.return_value.__enter__.return_value\n        eab_handler.eab_profile_get.return_value = {\n            \"profile\": \"value\",\n            \"subject\": \"value2\",\n        }\n        with patch(\n            \"acme_srv.helpers.eab.eab_profile_string_check\"\n        ) as mock_string_check:\n            self.eab_profile_revocation_check(\n                self.logger, self.cahandler, self.certificate_raw\n            )\n            mock_string_check.assert_called_once_with(\n                self.logger, self.cahandler, \"profile\", \"value\"\n            )\n            self.assertFalse(mock_b64_url_recode.called)\n\n    @patch(\"acme_srv.helpers.encoding.b64_url_recode\", return_value=\"encoded_cert\")\n    def test_456_eab_profile_revocation_check_list_value(self, mock_b64_url_recode):\n        \"\"\"eab_profile_dic with a list value\"\"\"\n        self.cahandler = MagicMock()\n        self.cahandler.eab_handler.return_value.__enter__.return_value = MagicMock()\n        self.certificate_raw = \"dummy_cert\"\n        eab_handler = self.cahandler.eab_handler.return_value.__enter__.return_value\n        eab_handler.eab_profile_get.return_value = {\"profile\": [\"v1\", \"v2\"]}\n        self.cahandler.eab_profile_list_check = MagicMock()\n        self.eab_profile_revocation_check(\n            self.logger, self.cahandler, self.certificate_raw\n        )\n        self.cahandler.eab_profile_list_check.assert_called_once()\n        self.assertFalse(mock_b64_url_recode.called)\n\n    @patch(\"acme_srv.helpers.encoding.b64_url_recode\", return_value=\"encoded_cert\")\n    def test_457_eab_profile_revocation_check_list_value_fallback(\n        self, mock_b64_url_recode\n    ):\n        \"\"\"eab_profile_dic with a list value, fallback to global function\"\"\"\n        self.cahandler = MagicMock()\n        self.cahandler.eab_handler.return_value.__enter__.return_value = MagicMock()\n        self.certificate_raw = \"dummy_cert\"\n        eab_handler = self.cahandler.eab_handler.return_value.__enter__.return_value\n        eab_handler.eab_profile_get.return_value = {\"profile\": [\"v1\", \"v2\"]}\n        if hasattr(self.cahandler, \"eab_profile_list_check\"):\n            delattr(self.cahandler, \"eab_profile_list_check\")\n        with patch(\"acme_srv.helpers.eab.eab_profile_list_check\") as mock_list_check:\n            self.eab_profile_revocation_check(\n                self.logger, self.cahandler, self.certificate_raw\n            )\n            mock_list_check.assert_called_once()\n\n    def test_458_missing_required_keys(self):\n        \"\"\"test handler_config_check() with missing required keys\"\"\"\n\n        class DummyHandler(object):\n            def __init__(self):\n                self.vault_url = \"url\"\n                self.vault_token = None\n\n        dummy_handler = DummyHandler()\n        required_keys = [\"vault_url\", \"vault_token\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"vault_token parameter is missing in config file\",\n                self.handler_config_check(self.logger, dummy_handler, required_keys),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: vault_token parameter is missing in config file\",\n            lcm.output,\n        )\n\n    def test_459_all_required_keys_present(self):\n        class DummyHandler(object):\n            def __init__(self):\n                self.vault_url = \"url\"\n                self.vault_token = \"token\"\n                self.another_param = \"param\"\n\n        dummy_handler = DummyHandler()\n        required_keys = [\"vault_url\", \"vault_token\"]\n        self.assertFalse(\n            self.handler_config_check(self.logger, dummy_handler, required_keys)\n        )\n\n    def test_460_empty_config(self):\n        class DummyHandler(object):\n            def __init__(self):\n                self.vault_url = \"url\"\n                self.vault_token = \"token\"\n\n        dummy_handler = DummyHandler()\n        required_keys = []\n        self.assertFalse(\n            self.handler_config_check(self.logger, dummy_handler, required_keys)\n        )\n\n    @patch(\"acme_srv.helpers.network.proxy_check\")\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_461_config_proxy_load_valid_config(self, mock_parse_url, mock_proxy_check):\n        \"\"\"test config_proxy_load() with valid configuration\"\"\"\n        config_dic = {\n            \"DEFAULT\": {\n                \"proxy_server_list\": '{\"example.com\": \"proxy.example.com:8080\", \"*.test.com\": \"proxy.test.com:3128\"}'\n            }\n        }\n        host_name = \"https://api.example.com:443/test\"\n\n        # Mock parse_url to return host information\n        mock_parse_url.return_value = {\"host\": \"api.example.com:443\"}\n        # Mock proxy_check to return a proxy server\n        mock_proxy_check.return_value = \"proxy.example.com:8080\"\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        expected = {\"http\": \"proxy.example.com:8080\", \"https\": \"proxy.example.com:8080\"}\n        self.assertEqual(result, expected)\n\n        # Verify the mocks were called correctly\n        mock_parse_url.assert_called_once_with(self.logger, host_name)\n        mock_proxy_check.assert_called_once_with(\n            self.logger,\n            \"api.example.com\",\n            {\n                \"example.com\": \"proxy.example.com:8080\",\n                \"*.test.com\": \"proxy.test.com:3128\",\n            },\n        )\n\n    def test_462_config_proxy_load_no_default_section(self):\n        \"\"\"test config_proxy_load() with no DEFAULT section\"\"\"\n        config_dic = {\"OTHER\": {\"some_setting\": \"value\"}}\n        host_name = \"https://api.example.com:443/test\"\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n\n    def test_463_config_proxy_load_no_proxy_server_list(self):\n        \"\"\"test config_proxy_load() with no proxy_server_list in DEFAULT section\"\"\"\n        config_dic = {\"DEFAULT\": {\"other_setting\": \"value\"}}\n        host_name = \"https://api.example.com:443/test\"\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n\n    def test_464_config_proxy_load_invalid_json(self):\n        \"\"\"test config_proxy_load() with invalid JSON in proxy_server_list\"\"\"\n        config_dic = {\"DEFAULT\": {\"proxy_server_list\": \"invalid json string\"}}\n        host_name = \"https://api.example.com:443/test\"\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n        # Check that warning message was logged\n        self.assertTrue(\n            any(\n                \"Failed to parse proxy_server_list from configuration:\" in log\n                for log in lcm.output\n            )\n        )\n\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_465_config_proxy_load_no_host_in_url(self, mock_parse_url):\n        \"\"\"test config_proxy_load() with parsed URL missing host information\"\"\"\n        config_dic = {\n            \"DEFAULT\": {\n                \"proxy_server_list\": '{\"example.com\": \"proxy.example.com:8080\"}'\n            }\n        }\n        host_name = \"invalid-url\"\n\n        # Mock parse_url to return empty dict (no host)\n        mock_parse_url.return_value = {}\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n        mock_parse_url.assert_called_once_with(self.logger, host_name)\n\n    @patch(\"acme_srv.helpers.network.proxy_check\")\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_466_config_proxy_load_proxy_check_returns_none(\n        self, mock_parse_url, mock_proxy_check\n    ):\n        \"\"\"test config_proxy_load() when proxy_check returns None\"\"\"\n        config_dic = {\n            \"DEFAULT\": {\"proxy_server_list\": '{\"other.com\": \"proxy.other.com:8080\"}'}\n        }\n        host_name = \"https://api.example.com:443/test\"\n\n        # Mock parse_url to return host information\n        mock_parse_url.return_value = {\"host\": \"api.example.com:443\"}\n        # Mock proxy_check to return None (no proxy needed)\n        mock_proxy_check.return_value = None\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        expected = {\"http\": None, \"https\": None}\n        self.assertEqual(result, expected)\n\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_467_config_proxy_load_parse_url_exception(self, mock_parse_url):\n        \"\"\"test config_proxy_load() when parse_url raises exception\"\"\"\n        config_dic = {\n            \"DEFAULT\": {\n                \"proxy_server_list\": '{\"example.com\": \"proxy.example.com:8080\"}'\n            }\n        }\n        host_name = \"https://api.example.com:443/test\"\n\n        # Mock parse_url to raise an exception\n        mock_parse_url.side_effect = Exception(\"URL parsing error\")\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n        # Check that warning message was logged\n        self.assertTrue(\n            any(\n                \"Failed to parse proxy_server_list from configuration: URL parsing error\"\n                in log\n                for log in lcm.output\n            )\n        )\n\n    @patch(\"acme_srv.helpers.network.proxy_check\")\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_468_config_proxy_load_host_without_port(\n        self, mock_parse_url, mock_proxy_check\n    ):\n        \"\"\"test config_proxy_load() with host that doesn't contain port\"\"\"\n        config_dic = {\n            \"DEFAULT\": {\n                \"proxy_server_list\": '{\"example.com\": \"proxy.example.com:8080\"}'\n            }\n        }\n        host_name = \"https://api.example.com/test\"\n\n        # Mock parse_url to return host without port\n        mock_parse_url.return_value = {\"host\": \"api.example.com\"}\n        # Mock proxy_check to return a proxy server\n        mock_proxy_check.return_value = \"proxy.example.com:8080\"\n\n        # This should cause an exception when trying to split on ':'\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        self.assertEqual(result, {})\n        # Check that warning message was logged due to the exception\n        self.assertTrue(\n            any(\n                \"Failed to parse proxy_server_list from configuration:\" in log\n                for log in lcm.output\n            )\n        )\n\n    @patch(\"acme_srv.helpers.network.proxy_check\")\n    @patch(\"acme_srv.helpers.network.parse_url\")\n    def test_469_config_proxy_load_empty_proxy_list(\n        self, mock_parse_url, mock_proxy_check\n    ):\n        \"\"\"test config_proxy_load() with empty proxy_server_list\"\"\"\n        config_dic = {\"DEFAULT\": {\"proxy_server_list\": \"{}\"}}\n        host_name = \"https://api.example.com:443/test\"\n\n        # Mock parse_url to return host information\n        mock_parse_url.return_value = {\"host\": \"api.example.com:443\"}\n        # Mock proxy_check to return None for empty proxy list\n        mock_proxy_check.return_value = None\n\n        result = self.config_proxy_load(self.logger, config_dic, host_name)\n\n        expected = {\"http\": None, \"https\": None}\n        self.assertEqual(result, expected)\n\n        mock_parse_url.assert_called_once_with(self.logger, host_name)\n        mock_proxy_check.assert_called_once_with(self.logger, \"api.example.com\", {})\n\n    def test_470_config_async_mode_load_true_with_django(self):\n        \"\"\"test config_async_mode_load() with async_mode True and django db\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {\"async_mode\": \"True\"}\n        db_type = \"django\"\n        result = self.config_async_mode_load(self.logger, config_dic, db_type)\n        self.assertTrue(result)\n\n    def test_471_config_async_mode_load_true_non_django(self):\n        \"\"\"test config_async_mode_load() with async_mode True and non-django db\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {\"async_mode\": \"True\"}\n        db_type = \"sqlite\"\n        with self.assertLogs(self.logger, level=\"INFO\") as log:\n            result = self.config_async_mode_load(self.logger, config_dic, db_type)\n        self.assertFalse(result)\n        self.assertIn(\"asynchronous Challenge validation disabled\", log.output[0])\n\n    def test_472_config_async_mode_load_false_with_django(self):\n        \"\"\"test config_async_mode_load() with async_mode False and django db\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {\"async_mode\": \"False\"}\n        db_type = \"django\"\n        self.assertFalse(self.config_async_mode_load(self.logger, config_dic, db_type))\n\n    def test_473_config_async_mode_load_default_fallback(self):\n        \"\"\"test config_async_mode_load() with no async_mode setting (fallback)\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {}\n        db_type = \"django\"\n        self.assertFalse(self.config_async_mode_load(self.logger, config_dic, db_type))\n\n    def test_474_config_async_mode_load_no_default_section(self):\n        \"\"\"test config_async_mode_load() with no DEFAULT section\"\"\"\n        config_dic = configparser.ConfigParser()\n        db_type = \"django\"\n        self.assertFalse(self.config_async_mode_load(self.logger, config_dic, db_type))\n\n    def test_475_config_async_mode_load_invalid_boolean(self):\n        \"\"\"test config_async_mode_load() with invalid boolean value\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {\"async_mode\": \"invalid\"}\n        db_type = \"django\"\n        # Invalid boolean values should raise a ValueError by getboolean()\n        with self.assertRaises(ValueError) as context:\n            self.config_async_mode_load(self.logger, config_dic, db_type)\n        self.assertIn(\"Not a boolean: invalid\", str(context.exception))\n\n    def test_476_config_async_mode_load_case_insensitive_true(self):\n        \"\"\"test config_async_mode_load() with case insensitive True values\"\"\"\n        test_cases = [\"true\", \"TRUE\", \"True\", \"1\", \"yes\", \"on\"]\n        for value in test_cases:\n            with self.subTest(value=value):\n                config_dic = configparser.ConfigParser()\n                config_dic[\"DEFAULT\"] = {\"async_mode\": value}\n                db_type = \"django\"\n                result = self.config_async_mode_load(self.logger, config_dic, db_type)\n                self.assertTrue(result)\n\n    def test_477_config_async_mode_load_case_insensitive_false(self):\n        \"\"\"test config_async_mode_load() with case insensitive False values\"\"\"\n        test_cases = [\"false\", \"FALSE\", \"False\", \"0\", \"no\", \"off\"]\n        for value in test_cases:\n            with self.subTest(value=value):\n                config_dic = configparser.ConfigParser()\n                config_dic[\"DEFAULT\"] = {\"async_mode\": value}\n                db_type = \"django\"\n                self.assertFalse(\n                    self.config_async_mode_load(self.logger, config_dic, db_type)\n                )\n\n    def test_478_config_async_mode_load_different_db_types(self):\n        \"\"\"test config_async_mode_load() with various non-django db types\"\"\"\n        config_dic = configparser.ConfigParser()\n        config_dic[\"DEFAULT\"] = {\"async_mode\": \"True\"}\n\n        db_types = [\"sqlite\", \"mysql\", \"postgresql\", \"oracle\", \"wsgi\", \"\"]\n        for db_type in db_types:\n            with self.subTest(db_type=db_type):\n                with self.assertLogs(self.logger, level=\"INFO\") as log:\n                    result = self.config_async_mode_load(\n                        self.logger, config_dic, db_type\n                    )\n                self.assertFalse(result)\n                self.assertIn(\n                    \"asynchronous Challenge validation disabled\", log.output[0]\n                )\n\n    def test_479_fqdn_resolve_successful_a_record_no_catch_all(self):\n        \"\"\"Test successful A record resolution without catch_all\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        # Create a proper mock that behaves like dns.resolver answer\n        mock_answer = [Mock(__str__=Mock(return_value=\"192.168.1.1\"))]\n        mock_resolver.resolve.return_value = mock_answer\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        self.assertEqual(result, \"192.168.1.1\")\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n        mock_resolver.resolve.assert_called_once_with(\"example.com\", \"A\")\n\n    def test_480_fqdn_resolve_successful_aaaa_record_no_catch_all(self):\n        \"\"\"Test successful AAAA record resolution when A record fails\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n\n        # A record fails, AAAA succeeds\n        mock_aaaa_answer = [Mock(__str__=Mock(return_value=\"2001:db8::1\"))]\n        mock_resolver.resolve.side_effect = [dns.resolver.NXDOMAIN(), mock_aaaa_answer]\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        self.assertEqual(result, \"2001:db8::1\")\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n\n    def test_481_fqdn_resolve_successful_catch_all_both_records(self):\n        \"\"\"Test successful resolution with catch_all returning both A and AAAA\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n\n        # Mock both A and AAAA responses\n        mock_a_answer = [\n            Mock(__str__=Mock(return_value=\"192.168.1.1\")),\n            Mock(__str__=Mock(return_value=\"192.168.1.2\")),\n        ]\n        mock_aaaa_answer = [Mock(__str__=Mock(return_value=\"2001:db8::1\"))]\n        mock_resolver.resolve.side_effect = [mock_a_answer, mock_aaaa_answer]\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=True\n        )\n\n        self.assertEqual(result, [\"192.168.1.1\", \"192.168.1.2\", \"2001:db8::1\"])\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n\n    def test_482_fqdn_resolve_nxdomain_error(self):\n        \"\"\"Test NXDOMAIN error handling\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_resolver.resolve.side_effect = dns.resolver.NXDOMAIN()\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"nonexistent.com\", catch_all=False\n        )\n\n        expected_result = None\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIn(\"NXDOMAIN: nonexistent.com does not exist\", error_msg)\n\n    def test_483_fqdn_resolve_no_answer_error(self):\n        \"\"\"Test NoAnswer error handling\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_resolver.resolve.side_effect = dns.resolver.NoAnswer()\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        expected_result = None\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIn(\"No A record found for example.com\", error_msg)\n\n    def test_484_fqdn_resolve_timeout_error(self):\n        \"\"\"Test timeout error handling\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_resolver.resolve.side_effect = dns.resolver.Timeout()\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        expected_result = None\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIn(\"DNS query timeout for example.com\", error_msg)\n\n    def test_485_fqdn_resolve_generic_error(self):\n        \"\"\"Test generic exception handling\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_resolver.resolve.side_effect = Exception(\"Connection refused\")\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        expected_result = None\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIn(\"DNS resolution error: Connection refused\", error_msg)\n\n    def test_486_fqdn_resolve_mixed_errors_catch_all(self):\n        \"\"\"Test mixed errors across record types with catch_all\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_resolver.resolve.side_effect = [\n            dns.resolver.NXDOMAIN(),  # A record fails\n            dns.resolver.Timeout(),  # AAAA record fails\n        ]\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=True\n        )\n\n        expected_result = []\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIn(\"A: NXDOMAIN: example.com does not exist\", error_msg)\n        self.assertIn(\"AAAA: DNS query timeout for example.com\", error_msg)\n\n    def test_487_fqdn_resolve_empty_answers_no_catch_all(self):\n        \"\"\"Test empty answers without catch_all\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_answer = []  # Empty list - no DNS records\n        mock_resolver.resolve.return_value = mock_answer\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        expected_result = None\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIsNone(error_msg)\n\n    def test_488_fqdn_resolve_empty_answers_catch_all(self):\n        \"\"\"Test empty answers with catch_all\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_answer = []  # Empty list - no DNS records\n        mock_resolver.resolve.return_value = mock_answer\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=True\n        )\n\n        expected_result = []\n        self.assertEqual(result, expected_result)\n        self.assertTrue(invalid)\n        self.assertIsNone(error_msg)\n\n    def test_489_fqdn_resolve_partial_success_catch_all(self):\n        \"\"\"Test partial success with catch_all (A succeeds, AAAA fails)\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_a_answer = [Mock(__str__=Mock(return_value=\"192.168.1.1\"))]\n        mock_resolver.resolve.side_effect = [\n            mock_a_answer,  # A record succeeds\n            dns.resolver.NoAnswer(),  # AAAA record fails\n        ]\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=True\n        )\n\n        self.assertEqual(result, [\"192.168.1.1\"])\n        self.assertFalse(invalid)  # Should be valid since at least one succeeded\n        self.assertIsNone(error_msg)\n\n    def test_490_fqdn_resolve_multiple_a_records_no_catch_all(self):\n        \"\"\"Test multiple A records without catch_all (should return first)\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_answer = [\n            Mock(__str__=Mock(return_value=\"192.168.1.1\")),\n            Mock(__str__=Mock(return_value=\"192.168.1.2\")),\n            Mock(__str__=Mock(return_value=\"192.168.1.3\")),\n        ]\n        mock_resolver.resolve.return_value = mock_answer\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        self.assertEqual(result, \"192.168.1.1\")\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n        # Should only make one call since it breaks after first success\n        mock_resolver.resolve.assert_called_once_with(\"example.com\", \"A\")\n\n    def test_491_fqdn_resolve_a_fails_aaaa_succeeds_no_catch_all(self):\n        \"\"\"Test A record failure but AAAA success without catch_all\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_aaaa_answer = [Mock(__str__=Mock(return_value=\"2001:db8::1\"))]\n        mock_resolver.resolve.side_effect = [\n            dns.resolver.NoAnswer(),  # A record fails\n            mock_aaaa_answer,  # AAAA record succeeds\n        ]\n\n        result, invalid, error_msg = _fqdn_resolve(\n            self.logger, mock_resolver, \"example.com\", catch_all=False\n        )\n\n        self.assertEqual(result, \"2001:db8::1\")\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n\n    def test_492_fqdn_resolve_logging_verification(self):\n        \"\"\"Test that appropriate logging occurs\"\"\"\n        from acme_srv.helpers.network import _fqdn_resolve\n\n        mock_resolver = Mock()\n        mock_answer = [Mock(__str__=Mock(return_value=\"192.168.1.1\"))]\n        mock_resolver.resolve.return_value = mock_answer\n\n        with self.assertLogs(self.logger, level=\"DEBUG\") as log_context:\n            result, invalid, error_msg = _fqdn_resolve(\n                self.logger, mock_resolver, \"example.com\", catch_all=False\n            )\n\n        self.assertEqual(result, \"192.168.1.1\")\n        self.assertFalse(invalid)\n        self.assertIsNone(error_msg)\n\n        # Verify debug logs\n        self.assertTrue(\n            any(\n                \"Helper._fqdn_resolve(example.com:False)\" in record.message\n                for record in log_context.records\n            )\n        )\n        self.assertTrue(\n            any(\n                \"Helper._fqdn_resolve() got answer:\" in record.message\n                for record in log_context.records\n            )\n        )\n        self.assertTrue(\n            any(\n                \"Helper._fqdn_resolve(example.com) ended with:\" in record.message\n                for record in log_context.records\n            )\n        )\n\n    def test_493_ptr_resolve_successful_resolution(self):\n        \"\"\"Test successful PTR record resolution\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n\n            # Mock successful PTR resolution\n            mock_ptr_answer = [Mock(__str__=Mock(return_value=\"example.com.\"))]\n            mock_resolver.resolve.return_value = mock_ptr_answer\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                result, invalid = ptr_resolve(self.logger, \"192.168.1.1\")\n\n                self.assertEqual(result, \"example.com\")  # Trailing dot removed\n                self.assertFalse(invalid)\n                mock_reverse.assert_called_once_with(\"192.168.1.1\")\n                mock_resolver.resolve.assert_called_once_with(\"reversed_ip\", \"PTR\")\n\n    def test_494_ptr_resolve_successful_with_dns_servers(self):\n        \"\"\"Test successful PTR resolution with custom DNS servers\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n\n            # Mock successful PTR resolution\n            mock_ptr_answer = [Mock(__str__=Mock(return_value=\"mail.example.com.\"))]\n            mock_resolver.resolve.return_value = mock_ptr_answer\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n                dns_servers = [\"8.8.8.8\", \"1.1.1.1\"]\n\n                result, invalid = ptr_resolve(self.logger, \"203.0.113.5\", dns_servers)\n\n                self.assertEqual(result, \"mail.example.com\")\n                self.assertFalse(invalid)\n                self.assertEqual(mock_resolver.nameservers, dns_servers)\n\n    def test_495_ptr_resolve_exception_handling(self):\n        \"\"\"Test PTR resolution exception handling\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n            mock_resolver.resolve.side_effect = Exception(\"DNS resolution failed\")\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                result, invalid = ptr_resolve(self.logger, \"invalid.ip\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n    def test_496_ptr_resolve_nxdomain_error(self):\n        \"\"\"Test PTR resolution with NXDOMAIN error\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n            mock_resolver.resolve.side_effect = dns.resolver.NXDOMAIN()\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                result, invalid = ptr_resolve(self.logger, \"10.0.0.1\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n    def test_497_ptr_resolve_timeout_error(self):\n        \"\"\"Test PTR resolution with timeout error\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n            mock_resolver.resolve.side_effect = dns.resolver.Timeout()\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                result, invalid = ptr_resolve(self.logger, \"172.16.0.1\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n    def test_498_ptr_resolve_invalid_address_format(self):\n        \"\"\"Test PTR resolution with invalid IP address format\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.side_effect = Exception(\"Invalid IP address format\")\n\n                result, invalid = ptr_resolve(self.logger, \"not.an.ip.address\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n    def test_499_ptr_resolve_empty_response(self):\n        \"\"\"Test PTR resolution with empty response\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n            mock_resolver.resolve.side_effect = IndexError(\"list index out of range\")\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                result, invalid = ptr_resolve(self.logger, \"198.51.100.1\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n    def test_500_ptr_resolve_logging_verification(self):\n        \"\"\"Test PTR resolution logging behavior\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n\n            # Mock successful PTR resolution\n            mock_ptr_answer = [Mock(__str__=Mock(return_value=\"test.example.org.\"))]\n            mock_resolver.resolve.return_value = mock_ptr_answer\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                with self.assertLogs(self.logger, level=\"DEBUG\") as log_context:\n                    result, invalid = ptr_resolve(self.logger, \"93.184.216.34\")\n\n                self.assertEqual(result, \"test.example.org\")\n                self.assertFalse(invalid)\n\n                # Verify debug logs\n                self.assertTrue(\n                    any(\n                        \"Helper.ptr_resolve(93.184.216.34)\" in record.message\n                        for record in log_context.records\n                    )\n                )\n                self.assertTrue(\n                    any(\n                        \"Helper.ptr_resolve(93.184.216.34) ended with:\"\n                        in record.message\n                        for record in log_context.records\n                    )\n                )\n\n    def test_501_ptr_resolve_error_logging_verification(self):\n        \"\"\"Test PTR resolution error logging behavior\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n            mock_resolver.resolve.side_effect = Exception(\"Connection timeout\")\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"reversed_ip\"\n\n                with self.assertLogs(self.logger, level=\"DEBUG\") as log_context:\n                    result, invalid = ptr_resolve(self.logger, \"127.0.0.1\")\n\n                self.assertIsNone(result)\n                self.assertTrue(invalid)\n\n                # Verify error logging\n                self.assertTrue(\n                    any(\n                        \"Error while resolving 127.0.0.1: Connection timeout\"\n                        in record.message\n                        for record in log_context.records\n                    )\n                )\n\n    def test_502_ptr_resolve_ipv6_address(self):\n        \"\"\"Test PTR resolution with IPv6 address\"\"\"\n        from acme_srv.helpers.network import ptr_resolve\n\n        with patch(\"dns.resolver.Resolver\") as mock_resolver_class:\n            mock_resolver = Mock()\n            mock_resolver_class.return_value = mock_resolver\n\n            # Mock successful PTR resolution for IPv6\n            mock_ptr_answer = [Mock(__str__=Mock(return_value=\"ipv6.example.com.\"))]\n            mock_resolver.resolve.return_value = mock_ptr_answer\n\n            with patch(\"dns.reversename.from_address\") as mock_reverse:\n                mock_reverse.return_value = \"ipv6_reversed\"\n\n                result, invalid = ptr_resolve(self.logger, \"2001:db8::1\")\n\n                self.assertEqual(result, \"ipv6.example.com\")\n                self.assertFalse(invalid)\n                mock_reverse.assert_called_once_with(\"2001:db8::1\")\n                mock_resolver.resolve.assert_called_once_with(\"ipv6_reversed\", \"PTR\")\n\n    def test_503_url_get_with_default_dns_successful_200(self):\n        \"\"\"Test successful HTTP request with 200 status code\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_response = Mock()\n                mock_response.text = \"Success response\"\n                mock_response.status_code = 200\n                mock_get.return_value = mock_response\n\n                result, status_code, error_msg = url_get_with_default_dns(\n                    self.logger, \"http://example.com\", {}, True, 30\n                )\n\n                self.assertEqual(result, \"Success response\")\n                self.assertEqual(status_code, 200)\n                self.assertIsNone(error_msg)\n                mock_get.assert_called_once_with(\n                    \"http://example.com\",\n                    verify=True,\n                    timeout=30,\n                    headers={\"User-Agent\": \"test\"},\n                    proxies={},\n                )\n\n    def test_504_url_get_with_default_dns_successful_non_200(self):\n        \"\"\"Test successful HTTP request with non-200 status code\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_response = Mock()\n                mock_response.text = \"Not found\"\n                mock_response.status_code = 404\n                mock_response.reason = \"Not Found\"\n                mock_get.return_value = mock_response\n\n                result, status_code, error_msg = url_get_with_default_dns(\n                    self.logger, \"http://example.com\", {}, True, 30\n                )\n\n                self.assertEqual(result, \"Not found\")\n                self.assertEqual(status_code, 404)\n                self.assertEqual(error_msg, \"http://example.com Not Found\")\n\n    def test_505_url_get_with_default_dns_exception_fallback_success(self):\n        \"\"\"Test exception in first request triggers IPv4 fallback - success\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                # First call fails, second call (fallback) succeeds\n                mock_response = Mock()\n                mock_response.text = \"Fallback success\"\n                mock_response.status_code = 200\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    mock_response,\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\") as mock_urllib3:\n                    old_gai_family = Mock()\n                    mock_urllib3.allowed_gai_family = old_gai_family\n\n                    result, status_code, error_msg = url_get_with_default_dns(\n                        self.logger,\n                        \"http://example.com\",\n                        {\"http\": \"proxy:8080\"},\n                        True,\n                        30,\n                    )\n\n                    self.assertEqual(result, \"Fallback success\")\n                    self.assertEqual(status_code, 200)\n                    self.assertIsNone(error_msg)\n                    # Verify fallback call\n                    self.assertEqual(mock_get.call_count, 2)\n                    # Verify old GAI family was restored\n                    self.assertEqual(mock_urllib3.allowed_gai_family, old_gai_family)\n\n    def test_506_url_get_with_default_dns_fallback_read_timeout(self):\n        \"\"\"Test ReadTimeout exception in IPv4 fallback\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    requests.exceptions.ReadTimeout(\"Read timeout\"),\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\") as mock_urllib3:\n                    old_gai_family = Mock()\n                    mock_urllib3.allowed_gai_family = old_gai_family\n\n                    result, status_code, error_msg = url_get_with_default_dns(\n                        self.logger, \"http://example.com\", {}, False, 10\n                    )\n\n                    self.assertIsNone(result)\n                    self.assertEqual(status_code, 500)\n                    self.assertEqual(\n                        error_msg,\n                        \"Could not fetch URL: http://example.com - Read timeout.\",\n                    )\n                    # Verify old GAI family was restored\n                    self.assertEqual(mock_urllib3.allowed_gai_family, old_gai_family)\n\n    def test_507_url_get_with_default_dns_fallback_connection_error(self):\n        \"\"\"Test ConnectionError exception in IPv4 fallback\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    requests.exceptions.ConnectionError(\"Connection failed\"),\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\") as mock_urllib3:\n                    old_gai_family = Mock()\n                    mock_urllib3.allowed_gai_family = old_gai_family\n\n                    result, status_code, error_msg = url_get_with_default_dns(\n                        self.logger, \"http://example.com\", {}, True, 20\n                    )\n\n                    self.assertIsNone(result)\n                    self.assertEqual(status_code, 500)\n                    self.assertEqual(\n                        error_msg,\n                        \"Could not fetch URL: http://example.com - Connection error.\",\n                    )\n                    # Verify old GAI family was restored\n                    self.assertEqual(mock_urllib3.allowed_gai_family, old_gai_family)\n\n    def test_508_url_get_with_default_dns_fallback_generic_exception(self):\n        \"\"\"Test generic exception in IPv4 fallback\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    RuntimeError(\"Unexpected error\"),\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\") as mock_urllib3:\n                    old_gai_family = Mock()\n                    mock_urllib3.allowed_gai_family = old_gai_family\n\n                    result, status_code, error_msg = url_get_with_default_dns(\n                        self.logger, \"http://example.com\", {}, True, 15\n                    )\n\n                    self.assertIsNone(result)\n                    self.assertEqual(status_code, 500)\n                    self.assertEqual(\n                        error_msg, \"Could not fetch URL: http://example.com\"\n                    )\n                    # Verify old GAI family was restored\n                    self.assertEqual(mock_urllib3.allowed_gai_family, old_gai_family)\n\n    def test_509_url_get_with_default_dns_fallback_non_200(self):\n        \"\"\"Test non-200 status in IPv4 fallback\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_response = Mock()\n                mock_response.text = \"Server Error\"\n                mock_response.status_code = 500\n                mock_response.reason = \"Internal Server Error\"\n\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    mock_response,\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\") as mock_urllib3:\n                    old_gai_family = Mock()\n                    mock_urllib3.allowed_gai_family = old_gai_family\n\n                    result, status_code, error_msg = url_get_with_default_dns(\n                        self.logger, \"http://example.com\", {}, True, 25\n                    )\n\n                    self.assertEqual(result, \"Server Error\")\n                    self.assertEqual(status_code, 500)\n                    self.assertEqual(\n                        error_msg, \"http://example.com Internal Server Error\"\n                    )\n                    # Verify old GAI family was restored\n                    self.assertEqual(mock_urllib3.allowed_gai_family, old_gai_family)\n\n    def test_510_url_get_with_default_dns_with_proxy_config(self):\n        \"\"\"Test request with proxy configuration\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_response = Mock()\n                mock_response.text = \"Proxy response\"\n                mock_response.status_code = 200\n                mock_get.return_value = mock_response\n\n                proxy_config = {\n                    \"http\": \"proxy.example.com:8080\",\n                    \"https\": \"proxy.example.com:8080\",\n                }\n\n                result, status_code, error_msg = url_get_with_default_dns(\n                    self.logger, \"http://example.com\", proxy_config, False, 30\n                )\n\n                self.assertEqual(result, \"Proxy response\")\n                self.assertEqual(status_code, 200)\n                self.assertIsNone(error_msg)\n                mock_get.assert_called_once_with(\n                    \"http://example.com\",\n                    verify=False,\n                    timeout=30,\n                    headers={\"User-Agent\": \"test\"},\n                    proxies=proxy_config,\n                )\n\n    def test_511_url_get_with_default_dns_logging_verification(self):\n        \"\"\"Test logging behavior in url_get_with_default_dns\"\"\"\n        from acme_srv.helpers.network import url_get_with_default_dns\n\n        with patch(\"acme_srv.helpers.network.v6_adjust\") as mock_v6_adjust:\n            mock_v6_adjust.return_value = ({\"User-Agent\": \"test\"}, \"http://example.com\")\n\n            with patch(\"requests.get\") as mock_get:\n                mock_get.side_effect = [\n                    Exception(\"Initial request failed\"),\n                    requests.exceptions.ReadTimeout(\"Read timeout\"),\n                ]\n\n                with patch(\"acme_srv.helpers.network.urllib3_cn\"):\n                    with self.assertLogs(self.logger, level=\"DEBUG\") as log_context:\n                        result, status_code, error_msg = url_get_with_default_dns(\n                            self.logger, \"http://example.com\", {}, True, 20\n                        )\n\n                    self.assertIsNone(result)\n                    self.assertEqual(status_code, 500)\n\n                    # Verify debug logs\n                    self.assertTrue(\n                        any(\n                            \"Helper.url_get_with_default_dns(http://example.com) vrf=True, timout:20\"\n                            in record.message\n                            for record in log_context.records\n                        )\n                    )\n                    self.assertTrue(\n                        any(\n                            \"Helper.url_get_with_default_dns(Initial request failed): error\"\n                            in record.message\n                            for record in log_context.records\n                        )\n                    )\n                    self.assertTrue(\n                        any(\n                            \"Helper.url_get_with_default_dns(http://example.com): fallback to v4\"\n                            in record.message\n                            for record in log_context.records\n                        )\n                    )\n                    self.assertTrue(\n                        any(\n                            \"Helper.url_get_with_default_dns(http://example.com): read timeout\"\n                            in record.message\n                            for record in log_context.records\n                        )\n                    )\n\n    def test_512_allowed_gai_family(self):\n        \"\"\"Test allowed_gai_family function returns IPv4\"\"\"\n        from acme_srv.helpers.network import allowed_gai_family\n        import socket\n\n        result = allowed_gai_family()\n\n        self.assertEqual(result, socket.AF_INET)\n\n    def test_513_config_allowed_domainlist_load_deprecated_section(self):\n        \"\"\"Test config_allowed_domainlist_load loads from deprecated CAhandler section and logs warning.\"\"\"\n        import logging\n        from acme_srv.helpers import config\n\n        # Simulate config_dic as a dict, as expected by the function\n        cfg = {\"CAhandler\": {\"allowed_domainlist\": \"example.com,example.org\"}}\n        logger = logging.getLogger(\"test_a2c\")\n        with self.assertLogs(logger, level=\"WARNING\") as log_context:\n            result = config.config_allowed_domainlist_load(logger, cfg)\n        from acme_srv.helpers.global_variables import PARSING_ERR_MSG\n\n        self.assertEqual(result, PARSING_ERR_MSG)\n        self.assertTrue(any(\"deprecated\" in msg.lower() for msg in log_context.output))\n\n    def test_514_config_allowed_domainlist_load_invalid_json(self):\n        \"\"\"Test config_allowed_domainlist_load handles invalid JSON and logs warning.\"\"\"\n        import logging\n        from acme_srv.helpers import config\n\n        logger = logging.getLogger(\"test_a2c\")\n        # Simulate a config dict with invalid JSON in Order section\n        cfg = {\"Order\": {\"allowed_domainlist\": \"not-a-json-list\"}}\n        with self.assertLogs(logger, level=\"WARNING\") as log_context:\n            from acme_srv.helpers.global_variables import PARSING_ERR_MSG\n\n            result = config.config_allowed_domainlist_load(logger, cfg)\n        self.assertEqual(result, PARSING_ERR_MSG)\n        self.assertTrue(\n            any(\n                \"failed to load allowed_domainlist\" in msg.lower()\n                for msg in log_context.output\n            )\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_housekeeping.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nfrom unittest.mock import patch, call, MagicMock, mock_open\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.challenge import Challenge\n        from acme_srv.housekeeping import Housekeeping\n\n        self.challenge = Challenge(False, \"http://tester.local\", self.logger)\n        self.housekeeping = Housekeeping(False, self.logger)\n\n    def test_001_housekeeping__certificatelist_get(self):\n        \"\"\"test Housekeeping._certificatelist_get()\"\"\"\n        self.housekeeping.dbstore.certificatelist_get.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.housekeeping._certificatelist_get())\n\n    def test_002_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - empty list\"\"\"\n        cert_list = []\n        self.assertEqual([], self.housekeeping._convert_data(cert_list))\n\n    def test_003_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - orders__expire to convert\"\"\"\n        cert_list = [{\"foo\": \"bar\", \"order.expires\": 1577840461}]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"order.expires\": \"2020-01-01 01:01:01\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_004_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - orders__expires and authentication__expires to convert (not in list)\"\"\"\n        cert_list = [\n            {\n                \"foo\": \"bar\",\n                \"order.expires\": 1577840461,\n                \"authentication.expires\": 1577840462,\n            }\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"authentication.expires\": 1577840462,\n                    \"foo\": \"bar\",\n                    \"order.expires\": \"2020-01-01 01:01:01\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_005_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - orders__expires and authorization__expires to convert (not in list)\"\"\"\n        cert_list = [\n            {\n                \"foo\": \"bar\",\n                \"order.expires\": 1577840461,\n                \"authorization.expires\": 1577840462,\n            }\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"authorization.expires\": \"2020-01-01 01:01:02\",\n                    \"foo\": \"bar\",\n                    \"order.expires\": \"2020-01-01 01:01:01\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_006_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list containing bogus values\"\"\"\n        cert_list = [{\"foo\": \"bar\"}]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_007_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list contains only issue_uts\"\"\"\n        cert_list = [{\"foo\": \"bar\", \"certificate.issue_uts\": 0}]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_008_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list contains only expire_uts\"\"\"\n        cert_list = [{\"foo\": \"bar\", \"certificate.expire_uts\": 0}]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_009_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list contains both issue_uts and expire_uts\"\"\"\n        cert_list = [\n            {\n                \"foo\": \"bar\",\n                \"certificate.expire_uts\": 1577840461,\n                \"certificate.issue_uts\": 1577840462,\n            }\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 1577840461,\n                    \"certificate.expire_date\": \"2020-01-01 01:01:01\",\n                    \"certificate.issue_date\": \"2020-01-01 01:01:02\",\n                    \"certificate.issue_uts\": 1577840462,\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_010_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list contains both uts with 0\"\"\"\n        cert_list = [\n            {\"foo\": \"bar\", \"certificate.expire_uts\": 0, \"certificate.issue_uts\": 0}\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_011_housekeeping__convert_data(self):\n        \"\"\"test Housekeeping._convert_data() - list contains both uts with 0 and a bogus cert_raw\"\"\"\n        cert_list = [\n            {\n                \"foo\": \"bar\",\n                \"certificate.expire_uts\": 0,\n                \"certificate.issue_uts\": 0,\n                \"certificate.cert_raw\": \"cert_raw\",\n            }\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 0,\n                    \"certificate.issue_uts\": 0,\n                    \"certificate.expire_date\": \"\",\n                    \"certificate.issue_date\": \"\",\n                    \"certificate.cert_raw\": \"cert_raw\",\n                    \"certificate.serial\": \"\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    @patch(\"acme_srv.housekeeping.cert_serial_get\")\n    @patch(\"acme_srv.housekeeping.cert_dates_get\")\n    def test_012_housekeeping__convert_data(self, mock_dates, mock_serial):\n        \"\"\"test Housekeeping._convert_data() - list contains both uts with 0 and a bogus cert_raw\"\"\"\n        cert_list = [\n            {\n                \"foo\": \"bar\",\n                \"certificate.expire_uts\": 0,\n                \"certificate.issue_uts\": 0,\n                \"certificate.cert_raw\": \"cert_raw\",\n            }\n        ]\n        mock_dates.return_value = (1577840461, 1577840462)\n        mock_serial.return_value = \"serial\"\n        self.assertEqual(\n            [\n                {\n                    \"foo\": \"bar\",\n                    \"certificate.expire_uts\": 1577840462,\n                    \"certificate.issue_uts\": 1577840461,\n                    \"certificate.serial\": \"serial\",\n                    \"certificate.expire_date\": \"2020-01-01 01:01:02\",\n                    \"certificate.issue_date\": \"2020-01-01 01:01:01\",\n                    \"certificate.cert_raw\": \"cert_raw\",\n                }\n            ],\n            self.housekeeping._convert_data(cert_list),\n        )\n\n    def test_013_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - both lists are empty\"\"\"\n        field_list = []\n        cert_list = []\n        self.assertEqual([], self.housekeeping._to_list(field_list, cert_list))\n\n    def test_014_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - cert_list is empty\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = []\n        self.assertEqual(\n            [[\"foo\", \"bar\"]], self.housekeeping._to_list(field_list, cert_list)\n        )\n\n    def test_015_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - one cert in list\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"foo1\", \"bar\": \"bar1\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_016_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - one incomplete cert in list\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"foo1\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_017_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - two certs in list\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"foo1\", \"bar\": \"bar1\"}, {\"foo\": \"foo2\", \"bar\": \"bar2\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"], [\"foo2\", \"bar2\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_018_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - two certs in list but on bogus\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"foo1\", \"bar\": \"bar1\"}, {\"foo\": \"foo2\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"], [\"foo2\", \"\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_019_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - one line contains LF\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"fo\\no1\", \"bar\": \"bar1\"}, {\"foo\": \"foo2\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"], [\"foo2\", \"\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_020_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - one line contains CRLF\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"fo\\r\\no1\", \"bar\": \"bar1\"}, {\"foo\": \"foo2\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"], [\"foo2\", \"\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_021_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - one line contains CR\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"fo\\ro1\", \"bar\": \"bar1\"}, {\"foo\": \"foo2\"}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", \"bar1\"], [\"foo2\", \"\"]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_022_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - integer in dictionary\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"fo\\ro1\", \"bar\": 100}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", 100]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_023_housekeeping__to_list(self):\n        \"\"\"test Housekeeping._to_list() - float in dictionary\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        cert_list = [{\"foo\": \"fo\\ro1\", \"bar\": 10.23}]\n        self.assertEqual(\n            [[\"foo\", \"bar\"], [\"foo1\", 10.23]],\n            self.housekeeping._to_list(field_list, cert_list),\n        )\n\n    def test_024_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - empty list\"\"\"\n        account_list = []\n        self.assertEqual([], self.housekeeping._to_acc_json(account_list))\n\n    def test_025_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - bogus list\"\"\"\n        account_list = [{\"foo\": \"bar\"}]\n        self.assertEqual(\n            [{\"error_list\": [{\"foo\": \"bar\"}]}],\n            self.housekeeping._to_acc_json(account_list),\n        )\n\n    def test_026_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - bogus list\"\"\"\n        account_list = [{\"account.name\": \"account.name\"}]\n        self.assertEqual(\n            [{\"error_list\": [{\"account.name\": \"account.name\"}]}],\n            self.housekeeping._to_acc_json(account_list),\n        )\n\n    def test_027_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - bogus list\"\"\"\n        account_list = [\n            {\"account.name\": \"account.name01\", \"order.name\": \"order.name01\"}\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"error_list\": [\n                        {\"account.name\": \"account.name01\", \"order.name\": \"order.name01\"}\n                    ]\n                }\n            ],\n            self.housekeeping._to_acc_json(account_list),\n        )\n\n    def test_028_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - bogus list\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n            }\n        ]\n        self.assertEqual(\n            [\n                {\n                    \"error_list\": [\n                        {\n                            \"account.name\": \"account.name01\",\n                            \"authorization.name\": \"authorization.name01\",\n                            \"order.name\": \"order.name01\",\n                        }\n                    ]\n                }\n            ],\n            self.housekeeping._to_acc_json(account_list),\n        )\n\n    def test_029_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - complete list\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            }\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name01\"}],\n                            }\n                        ],\n                    }\n                ],\n            }\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_030_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - two challenges\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            },\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name02\",\n            },\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [\n                                    {\"challenge.name\": \"challenge.name01\"},\n                                    {\"challenge.name\": \"challenge.name02\"},\n                                ],\n                            }\n                        ],\n                    }\n                ],\n            }\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_031_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - two authorizations\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            },\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name02\",\n                \"challenge.name\": \"challenge.name02\",\n            },\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name01\"}],\n                            },\n                            {\n                                \"authorization.name\": \"authorization.name02\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name02\"}],\n                            },\n                        ],\n                    }\n                ],\n            }\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_032_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - two orders\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            },\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name02\",\n                \"authorization.name\": \"authorization.name02\",\n                \"challenge.name\": \"challenge.name02\",\n            },\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name01\"}],\n                            }\n                        ],\n                    },\n                    {\n                        \"order.name\": \"order.name02\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name02\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name02\"}],\n                            }\n                        ],\n                    },\n                ],\n            }\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_033_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - two accounts\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            },\n            {\n                \"account.name\": \"account.name02\",\n                \"order.name\": \"order.name02\",\n                \"authorization.name\": \"authorization.name02\",\n                \"challenge.name\": \"challenge.name02\",\n            },\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name01\"}],\n                            }\n                        ],\n                    }\n                ],\n            },\n            {\n                \"account.name\": \"account.name02\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name02\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name02\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name02\"}],\n                            }\n                        ],\n                    }\n                ],\n            },\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_034_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - complete list with subkeys\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"account.foo\": \"account.foo\",\n                \"order.name\": \"order.name01\",\n                \"order.foo\": \"order.foo\",\n                \"authorization.name\": \"authorization.name01\",\n                \"authorization.foo\": \"authorization.foo\",\n                \"challenge.name\": \"challenge.name01\",\n                \"challenge.foo\": \"challenge.foo\",\n            }\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"account.foo\": \"account.foo\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"order.foo\": \"order.foo\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"authorization.foo\": \"authorization.foo\",\n                                \"challenges\": [\n                                    {\n                                        \"challenge.name\": \"challenge.name01\",\n                                        \"challenge.foo\": \"challenge.foo\",\n                                    }\n                                ],\n                            }\n                        ],\n                    }\n                ],\n            }\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_035_housekeeping__to_acc_json(self):\n        \"\"\"test Housekeeping._to_acc_list() - complete list\"\"\"\n        account_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"order.name\": \"order.name01\",\n                \"authorization.name\": \"authorization.name01\",\n                \"challenge.name\": \"challenge.name01\",\n            },\n            {\"foo\": \"bar\"},\n        ]\n        result_list = [\n            {\n                \"account.name\": \"account.name01\",\n                \"orders\": [\n                    {\n                        \"order.name\": \"order.name01\",\n                        \"authorizations\": [\n                            {\n                                \"authorization.name\": \"authorization.name01\",\n                                \"challenges\": [{\"challenge.name\": \"challenge.name01\"}],\n                            }\n                        ],\n                    }\n                ],\n            },\n            {\"error_list\": [{\"foo\": \"bar\"}]},\n        ]\n        self.assertEqual(result_list, self.housekeeping._to_acc_json(account_list))\n\n    def test_036_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - empty field_list\"\"\"\n        field_list = {}\n        prefix = \"prefix\"\n        self.assertEqual({}, self.housekeeping._fieldlist_normalize(field_list, prefix))\n\n    def test_037_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - one ele\"\"\"\n        field_list = [\"foo__bar\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"foo__bar\": \"foo.bar\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_038_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - two ele\"\"\"\n        field_list = [\"foo__bar\", \"bar__foo\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"bar__foo\": \"bar.foo\", \"foo__bar\": \"foo.bar\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_039_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - one ele without __\"\"\"\n        field_list = [\"foo\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"foo\": \"prefix.foo\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_040_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - two ele without __\"\"\"\n        field_list = [\"foo\", \"bar\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"foo\": \"prefix.foo\", \"bar\": \"prefix.bar\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_041_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - status handling\"\"\"\n        field_list = [\"foo__status__name\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"foo__status__name\": \"foo.status.name\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_042_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - status handling\"\"\"\n        field_list = [\"foo__bar__name\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"foo__bar__name\": \"bar.name\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_043_housekeeping__fieldlist_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - status handling\"\"\"\n        field_list = [\"status__name\"]\n        prefix = \"prefix\"\n        self.assertEqual(\n            {\"status__name\": \"status.name\"},\n            self.housekeeping._fieldlist_normalize(field_list, prefix),\n        )\n\n    def test_044_housekeeping__lists_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - one value\"\"\"\n        field_list = [\"foo\", \"foo__bar\", \"bar__foo\"]\n        value_list = [{\"foo__bar\": \"foo\", \"bar__foo\": \"bar\", \"foo\": \"foobar\"}]\n        prefix = \"prefix\"\n        self.assertEqual(\n            (\n                [\"prefix.foo\", \"foo.bar\", \"bar.foo\"],\n                [{\"foo.bar\": \"foo\", \"bar.foo\": \"bar\", \"prefix.foo\": \"foobar\"}],\n            ),\n            self.housekeeping._lists_normalize(field_list, value_list, prefix),\n        )\n\n    def test_045_housekeeping__lists_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - two values\"\"\"\n        field_list = [\"foo\", \"foo__bar\", \"bar__foo\"]\n        value_list = [\n            {\"foo__bar\": \"foo1\", \"bar__foo\": \"bar1\", \"foo\": \"foobar1\"},\n            {\"foo__bar\": \"foo2\", \"bar__foo\": \"bar2\", \"foo\": \"foobar2\"},\n        ]\n        prefix = \"prefix\"\n        result = (\n            [\"prefix.foo\", \"foo.bar\", \"bar.foo\"],\n            [\n                {\"foo.bar\": \"foo1\", \"bar.foo\": \"bar1\", \"prefix.foo\": \"foobar1\"},\n                {\"bar.foo\": \"bar2\", \"foo.bar\": \"foo2\", \"prefix.foo\": \"foobar2\"},\n            ],\n        )\n        self.assertEqual(\n            result, self.housekeeping._lists_normalize(field_list, value_list, prefix)\n        )\n\n    def test_046_housekeeping__lists_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - ele in field list without being in value list\"\"\"\n        field_list = [\"foo\", \"foo__bar\", \"bar__foo\"]\n        value_list = [{\"foo__bar\": \"foo\", \"bar__foo\": \"bar\"}]\n        prefix = \"prefix\"\n        self.assertEqual(\n            (\n                [\"prefix.foo\", \"foo.bar\", \"bar.foo\"],\n                [{\"bar.foo\": \"bar\", \"foo.bar\": \"foo\"}],\n            ),\n            self.housekeeping._lists_normalize(field_list, value_list, prefix),\n        )\n\n    def test_047_housekeeping__lists_normalize(self):\n        \"\"\"test Certificate._fieldlist_normalize() - ele in value list without being in field list\"\"\"\n        field_list = [\"foo__bar\"]\n        value_list = [{\"foo__bar\": \"foo\", \"bar__foo\": \"bar\"}]\n        prefix = \"prefix\"\n        self.assertEqual(\n            ([\"foo.bar\"], [{\"foo.bar\": \"foo\"}]),\n            self.housekeeping._lists_normalize(field_list, value_list, prefix),\n        )\n\n    def test_048_housekeeping__accountlist_get(self):\n        \"\"\"test Housekeeping._accountlist_get - dbstore.accountlist_get() raises an exception\"\"\"\n        self.housekeeping.dbstore.accountlist_get.side_effect = Exception(\n            \"exc_house_acc_get\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping._accountlist_get()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to retrieve account list: exc_house_acc_get\",\n            lcm.output,\n        )\n\n    def test_049_housekeeping__certificatelist_get(self):\n        \"\"\"test Housekeeping._certificatelist_get - dbstore.certificatelist_get() raises an exception\"\"\"\n        self.housekeeping.dbstore.certificatelist_get.side_effect = Exception(\n            \"exc_house_cert_get\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping._certificatelist_get()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to retrieve certificate list: exc_house_cert_get\",\n            lcm.output,\n        )\n\n    def test_050_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load  - version match int\"\"\"\n        self.housekeeping.dbstore.dbversion_get.return_value = (1, \"foo\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.housekeeping.dbversion_check(1)\n        self.assertIn(\"DEBUG:test_a2c:Database version: 1 is upto date\", lcm.output)\n\n    def test_051_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load  - version match float\"\"\"\n        self.housekeeping.dbstore.dbversion_get.return_value = (1.0, \"foo\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.housekeeping.dbversion_check(1.0)\n        self.assertIn(\n            \"DEBUG:test_a2c:Housekeeping.dbversion_check(1.0)\",\n            lcm.output,\n        )\n\n    def test_052_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load  - version match string\"\"\"\n        self.housekeeping.dbstore.dbversion_get.return_value = (\"1.0-devel\", \"foo\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.housekeeping.dbversion_check(\"1.0-devel\")\n        self.assertIn(\n            \"DEBUG:test_a2c:Housekeeping.dbversion_check(1.0-devel)\",\n            lcm.output,\n        )\n\n    def test_053_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load  - no version number specified\"\"\"\n        # self.signature.dbstore.jwk_load.side_effect = Exception('exc_sig_jw_load')\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping.dbversion_check()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database version could not be verified.\",\n            lcm.output,\n        )\n\n    def test_054_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load - version mismatch\"\"\"\n        self.housekeeping.dbstore.dbversion_get.return_value = (1, \"foo\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping.dbversion_check(2)\n        self.assertIn(\n            'CRITICAL:test_a2c:Database version mismatch: current version is 1 but should be 2. Please run the \"foo\" script',\n            lcm.output,\n        )\n\n    def test_055_housekeeping_dbversion_check(self):\n        \"\"\"test Housekeeping.dbversion_check load - version mismatch\"\"\"\n        self.housekeeping.dbstore.dbversion_get.side_effect = Exception(\n            \"exc_dbversion_chk\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping.dbversion_check(2)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check database version: exc_dbversion_chk\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._config_load\")\n    def test_056__enter__(self, mock_cfg):\n        \"\"\"test enter\"\"\"\n        mock_cfg.return_value = True\n        self.housekeeping.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"acme_srv.housekeeping.load_config\")\n    def test_057_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['Account'] = {'foo': 'bar'}\n        mock_load_cfg.return_value = parser\n        self.housekeeping._config_load()\n        self.assertTrue(mock_load_cfg.called)\n\n    @patch(\"acme_srv.housekeeping.load_config\")\n    def test_058_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Housekeeping\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.housekeeping._config_load()\n        self.assertTrue(mock_load_cfg.called)\n\n    @patch(\"csv.writer\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    def test_059__csv_dump(self, mock_write):\n        \"\"\"test csv dump\"\"\"\n        self.housekeeping._csv_dump(\"filename\", \"data\")\n        self.assertTrue(mock_write.called)\n\n    @patch(\"json.dumps\")\n    @patch(\"builtins.open\", mock_open(read_data=\"csv_dump\"), create=True)\n    def test_060__csv_dump(self, mock_json):\n        \"\"\"test csv dump\"\"\"\n        mock_json.return_value = {\"foo\": \"bar\"}\n        self.housekeeping._json_dump(\"filename\", \"data\")\n        self.assertTrue(mock_json.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._accountlist_get\")\n    def test_061_accountreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_tolist\n    ):\n        \"\"\"test accountreport_get() no report name\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = (\"foo\", \"bar\")\n        mock_convert.return_value = [\"list\"]\n        mock_tolist.return_value = [\"to_list\"]\n        self.assertEqual(\n            [\"to_list\"], self.housekeeping.accountreport_get(\"csv\", None, False)\n        )\n        self.assertTrue(mock_get.called)\n        self.assertTrue(mock_norm.called)\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_tolist.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._accountlist_get\")\n    def test_062_accountreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() report name csv\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = (\"foo\", \"bar\")\n        mock_convert.return_value = [\"list\"]\n        mock_list.return_value = [\"to_list\"]\n        self.assertEqual(\n            [\"to_list\"],\n            self.housekeeping.accountreport_get(\"csv\", \"report_name\", False),\n        )\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_dump.called)\n        self.assertTrue(mock_convert.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_acc_json\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._accountlist_get\")\n    def test_063_accountreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() report name json not nested\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = (\"foo\", \"bar\")\n        mock_convert.return_value = [\"list\"]\n        self.assertEqual(\n            [\"list\"], self.housekeeping.accountreport_get(\"json\", \"report_name\", False)\n        )\n        self.assertFalse(mock_list.called)\n        self.assertTrue(mock_dump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_acc_json\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._accountlist_get\")\n    def test_064_accountreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() report name json not nested\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = (\"foo\", \"bar\")\n        mock_convert.return_value = [\"list\"]\n        mock_list.return_value = [\"list1\"]\n        self.assertEqual(\n            [\"list1\"], self.housekeeping.accountreport_get(\"json\", \"report_name\", True)\n        )\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_dump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._certificatelist_get\")\n    def test_065_certreport_get(self, mock_get, mock_norm, mock_convert, mock_list):\n        \"\"\"test accountreport_get() no report name\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = ([\"foo\"], \"bar\")\n        mock_convert.return_value = [\"list\"]\n        mock_list.return_value = [\"to_list\"]\n        self.assertEqual([\"to_list\"], self.housekeeping.certreport_get(\"csv\", None))\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_list.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._certificatelist_get\")\n    def test_066_certreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() no report name\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = ([\"foo\"], \"bar\")\n        mock_convert.return_value = [\"list\"]\n        mock_list.return_value = [\"to_list\"]\n        self.assertEqual(\n            [\"to_list\"], self.housekeeping.certreport_get(\"csv\", \"report_name\")\n        )\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_dump.called)\n        self.assertTrue(mock_convert.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_acc_json\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._certificatelist_get\")\n    def test_067_certreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() report name json not nested\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = ([\"foo\"], \"bar\")\n        mock_convert.return_value = [\"list\"]\n        self.assertEqual(\n            [\"list\"], self.housekeeping.certreport_get(\"json\", \"report_name\")\n        )\n        self.assertFalse(mock_list.called)\n        self.assertTrue(mock_dump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_acc_json\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._certificatelist_get\")\n    def test_068_certreport_get(\n        self, mock_get, mock_norm, mock_convert, mock_list, mock_dump\n    ):\n        \"\"\"test accountreport_get() report name json not nested\"\"\"\n        mock_get.return_value = (\"foo\", \"bar\")\n        mock_norm.return_value = ([\"foo\"], \"bar\")\n        mock_convert.return_value = [\"list\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                [\"list\"], self.housekeeping.certreport_get(\"unknown\", \"report_name\")\n            )\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_dump.called)\n        self.assertIn(\n            \"INFO:test_a2c:No dump just return report\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.certificate.Certificate.dates_update\")\n    def test_069_certificate_data_update(self, mock_update):\n        \"\"\"test certificate_dates_update\"\"\"\n        self.housekeeping.certificate_dates_update()\n        self.assertTrue(mock_update.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_070_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup no uts empty report_name\"\"\"\n        mock_uts.return_value = 1111111111\n        mock_cleanup.return_value = (\"fieldlist\", [])\n        self.assertFalse(\n            self.housekeeping.certificates_cleanup(\n                uts=None, purge=False, report_format=\"csv\", report_name=None\n            )\n        )\n        self.assertTrue(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_071_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup no uts empty report_name\"\"\"\n        mock_uts.return_value = 1111111111\n        mock_cleanup.return_value = (\"fieldlist\", [])\n        self.assertFalse(\n            self.housekeeping.certificates_cleanup(\n                uts=None, purge=False, report_format=\"csv\", report_name=None\n            )\n        )\n        self.assertTrue(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_072_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup uts but empty report_name\"\"\"\n        mock_uts.return_value = 1111111111\n        mock_cleanup.return_value = (\"fieldlist\", [])\n        self.assertFalse(\n            self.housekeeping.certificates_cleanup(\n                uts=\"uts\", purge=False, report_format=\"csv\", report_name=None\n            )\n        )\n        self.assertFalse(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_073_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup no uts empty certlist\"\"\"\n        mock_uts.return_value = 111111111\n        mock_cleanup.return_value = (\"fieldlist\", [])\n        self.assertFalse(\n            self.housekeeping.certificates_cleanup(\n                uts=\"foo\", purge=False, report_format=\"csv\", report_name=\"foo\"\n            )\n        )\n        self.assertFalse(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_074_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup csv\"\"\"\n        mock_uts.return_value = 111111111\n        mock_cleanup.return_value = (\"fieldlist\", \"cert_list\")\n        self.assertEqual(\n            \"cert_list\",\n            self.housekeeping.certificates_cleanup(\n                uts=\"foo\", purge=False, report_format=\"csv\", report_name=\"foo\"\n            ),\n        )\n        self.assertFalse(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_075_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup json\"\"\"\n        mock_uts.return_value = 111111111\n        mock_cleanup.return_value = (\"fieldlist\", \"cert_list\")\n        self.assertEqual(\n            \"cert_list\",\n            self.housekeeping.certificates_cleanup(\n                uts=\"foo\", purge=False, report_format=\"json\", report_name=\"foo\"\n            ),\n        )\n        self.assertFalse(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.certificate.Certificate.cleanup\")\n    @patch(\"acme_srv.housekeeping.uts_now\")\n    def test_076_certificates_cleanup(\n        self, mock_uts, mock_cleanup, mock_list, mock_cdump, mock_jdump\n    ):\n        \"\"\"test certificates_cleanup unknown output\"\"\"\n        mock_uts.return_value = 111111111\n        mock_cleanup.return_value = (\"fieldlist\", \"cert_list\")\n        self.assertEqual(\n            \"cert_list\",\n            self.housekeeping.certificates_cleanup(\n                uts=\"foo\", purge=False, report_format=\"unkown\", report_name=\"foo\"\n            ),\n        )\n        self.assertFalse(mock_uts.called)\n        self.assertTrue(mock_cleanup.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.authorization.Authorization.invalidate\")\n    def test_077_authorizations_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization without report name\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.authorizations_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=None\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.authorization.Authorization.invalidate\")\n    def test_078_authorizations_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name but empty auth_list\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = []\n        self.housekeeping.authorizations_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.authorization.Authorization.invalidate\")\n    def test_079_authorizations_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.authorizations_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.authorization.Authorization.invalidate\")\n    def test_080_authorizations_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.authorizations_invalidate(\n            uts=\"foo\", report_format=\"csv\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.authorization.Authorization.invalidate\")\n    def test_081_authorizations_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.authorizations_invalidate(\n            uts=\"foo\", report_format=\"json\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.order.Order.invalidate\")\n    def test_082_orders_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization without report name\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.orders_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=None\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.order.Order.invalidate\")\n    def test_083_orders_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name but empty auth_list\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = []\n        self.housekeeping.orders_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.order.Order.invalidate\")\n    def test_084_orders_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.orders_invalidate(\n            uts=\"foo\", report_format=\"unkown\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.order.Order.invalidate\")\n    def test_085_orders_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.orders_invalidate(\n            uts=\"foo\", report_format=\"csv\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_list.called)\n        self.assertTrue(mock_cdump.called)\n        self.assertFalse(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._json_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._csv_dump\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._to_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._convert_data\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._lists_normalize\")\n    @patch(\"acme_srv.order.Order.invalidate\")\n    def test_086_orders_invalidate(\n        self,\n        mock_invalidate,\n        mock_normalize,\n        mock_convert,\n        mock_list,\n        mock_cdump,\n        mock_jdump,\n    ):\n        \"\"\"authorization with report name unknown report format\"\"\"\n        mock_invalidate.return_value = (\"fieldlist\", \"cert_list\")\n        mock_normalize.return_value = (\"field_list\", \"authorization_list\")\n        mock_convert.return_value = \"authorization_list\"\n        self.housekeeping.orders_invalidate(\n            uts=\"foo\", report_format=\"json\", report_name=\"foo\"\n        )\n        self.assertTrue(mock_invalidate.called)\n        self.assertTrue(mock_normalize.called)\n        self.assertTrue(mock_convert.called)\n        self.assertFalse(mock_list.called)\n        self.assertFalse(mock_cdump.called)\n        self.assertTrue(mock_jdump.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_087_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed\"\"\"\n        payload = {}\n        mock_check.return_value = (\n            400,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        result = {\n            \"code\": 400,\n            \"header\": {},\n            \"data\": {\"detail\": \"detail\", \"status\": 400, \"type\": \"message\"},\n        }\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertFalse(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_088_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed empty payload\"\"\"\n        payload = {}\n        mock_check.return_value = (\n            200,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        result = {\n            \"code\": 400,\n            \"header\": {},\n            \"data\": {\n                \"detail\": \"either type field or data field is missing in payload\",\n                \"status\": 400,\n                \"type\": \"urn:ietf:params:acme:error:malformed\",\n            },\n        }\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertFalse(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_089_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed data field missing\"\"\"\n        payload = {\"type\": \"type\"}\n        mock_check.return_value = (\n            200,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        result = {\n            \"code\": 400,\n            \"header\": {},\n            \"data\": {\n                \"detail\": \"either type field or data field is missing in payload\",\n                \"status\": 400,\n                \"type\": \"urn:ietf:params:acme:error:malformed\",\n            },\n        }\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertFalse(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_090_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed type field missing\"\"\"\n        payload = {\"data\": \"data\"}\n        mock_check.return_value = (\n            200,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        result = {\n            \"code\": 400,\n            \"header\": {},\n            \"data\": {\n                \"detail\": \"either type field or data field is missing in payload\",\n                \"status\": 400,\n                \"type\": \"urn:ietf:params:acme:error:malformed\",\n            },\n        }\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertFalse(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_091_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed unknown type\"\"\"\n        payload = {\"type\": \"type\", \"data\": \"data\"}\n        mock_check.return_value = (\n            200,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        result = {\n            \"code\": 400,\n            \"header\": {},\n            \"data\": {\n                \"detail\": \"unknown type value\",\n                \"status\": 400,\n                \"type\": \"urn:ietf:params:acme:error:malformed\",\n            },\n        }\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertFalse(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._clireport_get\")\n    @patch(\"acme_srv.message.Message.cli_check\")\n    def test_092_parse(self, mock_check, mock_report):\n        \"\"\"test parse cli_check() failed successfull report execution\"\"\"\n        payload = {\"type\": \"report\", \"data\": \"data\"}\n        mock_check.return_value = (\n            200,\n            \"message\",\n            \"detail\",\n            \"protected\",\n            payload,\n            \"account_name\",\n            {\"reportadmin\": True, \"foo\": \"bar\"},\n        )\n        mock_report.return_value = (\n            200,\n            \"rep_message\",\n            \"rep_det\",\n            {\"rep_foo\": \"rep_bar\"},\n        )\n        result = {\"code\": 200, \"header\": {}, \"rep_foo\": \"rep_bar\"}\n        self.assertEqual(result, self.housekeeping.parse(\"content\"))\n        self.assertTrue(mock_report.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_093_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() - reportadmin flag does not exist\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"accounts\", \"format\": \"json\"}}\n        permission_dic = {\"foo\": \"bar\"}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (\n            403,\n            \"urn:ietf:params:acme:error:unauthorized\",\n            \"No permissions to download reports\",\n            {},\n        )\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_094_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() - reportadmin flag does not exist\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"accounts\", \"format\": \"json\"}}\n        permission_dic = {\"reportadmin\": False}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (\n            403,\n            \"urn:ietf:params:acme:error:unauthorized\",\n            \"No permissions to download reports\",\n            {},\n        )\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_095_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() -account report\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"accounts\", \"format\": \"json\"}}\n        permission_dic = {\"reportadmin\": True}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (200, None, None, {\"data\": \"account_value\"})\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertTrue(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_096_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() -cert report\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"certificates\", \"format\": \"json\"}}\n        permission_dic = {\"reportadmin\": True}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (200, None, None, {\"data\": \"cert_value\"})\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertTrue(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_097_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() - unknown report\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"unknown\", \"format\": \"json\"}}\n        permission_dic = {\"reportadmin\": True}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (\n            400,\n            \"urn:ietf:params:acme:error:malformed\",\n            \"unknown report type\",\n            {},\n        )\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_098_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() - name tag is missing\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"foo\": \"unknown\", \"format\": \"json\"}}\n        permission_dic = {\"reportadmin\": True}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (\n            400,\n            \"urn:ietf:params:acme:error:malformed\",\n            \"unknown report type\",\n            {},\n        )\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping.accountreport_get\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.certreport_get\")\n    def test_099_clireport_get(self, mock_cert, mock_account):\n        \"\"\"test parse _clireport_get() - unknown format\"\"\"\n        payload = {\"type\": \"report\", \"data\": {\"name\": \"certificates\", \"format\": \"txt\"}}\n        permission_dic = {\"reportadmin\": True}\n        mock_cert.return_value = \"cert_value\"\n        mock_account.return_value = \"account_value\"\n        result = (\n            400,\n            \"urn:ietf:params:acme:error:malformed\",\n            \"unknown report format\",\n            {},\n        )\n        self.assertEqual(\n            result, self.housekeeping._clireport_get(payload, permission_dic)\n        )\n        self.assertFalse(mock_cert.called)\n        self.assertFalse(mock_account.called)\n\n    def test_100__cliconfig_check(self):\n        \"\"\"test _cliconfig_check - with empty input\"\"\"\n        config_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping._cliconfig_check(config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:Error: cliuser_mgmt.py config_check() failed: Either jwkname or jwk must be specified\",\n            lcm.output,\n        )\n\n    def test_101__cliconfig_check(self):\n        \"\"\"test _cliconfig_check - wrong input\"\"\"\n        config_dic = {\"foo\": \"bar\", \"bar\": \"foo\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping._cliconfig_check(config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:Error: cliuser_mgmt.py config_check() failed: Either jwkname or jwk must be specified\",\n            lcm.output,\n        )\n\n    def test_102__cliconfig_check(self):\n        \"\"\"test _cliconfig_check - list parameter is in\"\"\"\n        config_dic = {\"foo\": \"bar\", \"list\": \"list\"}\n        self.assertTrue(self.housekeeping._cliconfig_check(config_dic))\n\n    def test_103__cliconfig_check(self):\n        \"\"\"test _cliconfig_check - jwkname parameter is in\"\"\"\n        config_dic = {\"foo\": \"bar\", \"jwkname\": \"jwkname\"}\n        self.assertTrue(self.housekeeping._cliconfig_check(config_dic))\n\n    def test_104__cliconfig_check(self):\n        \"\"\"test _cliconfig_check - jwk parameter is in\"\"\"\n        config_dic = {\"foo\": \"bar\", \"jwk\": \"jwk\"}\n        self.assertTrue(self.housekeeping._cliconfig_check(config_dic))\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_format\")\n    def test_105__cliaccounts_list(self, mock_caf):\n        \"\"\"test _cliaccounts_list silent false\"\"\"\n        self.housekeeping.dbstore.cliaccountlist_get.return_value = \"foo\"\n        mock_caf.return_value = \"mock_caf\"\n        self.assertEqual(\"foo\", self.housekeeping._cliaccounts_list(False))\n        self.assertTrue(mock_caf.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_format\")\n    def test_106__cliaccounts_list(self, mock_caf):\n        \"\"\"test _cliaccounts_list silent true\"\"\"\n        self.housekeeping.dbstore.cliaccountlist_get.return_value = \"foo\"\n        mock_caf.return_value = \"mock_caf\"\n        self.assertEqual(\"foo\", self.housekeeping._cliaccounts_list(True))\n        self.assertFalse(mock_caf.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_format\")\n    def test_107__cliaccounts_list(self, mock_caf):\n        \"\"\"test _cliaccounts_list silent true\"\"\"\n        self.housekeeping.dbstore.cliaccountlist_get.side_effect = Exception(\"exc_calg\")\n        mock_caf.return_value = \"mock_caf\"\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertFalse(self.housekeeping._cliaccounts_list(False))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to retrieve CLI account list: exc_calg\",\n            lcm.output,\n        )\n        self.assertFalse(mock_caf.called)\n\n    @patch(\"builtins.print\")\n    def test_108__cliaccounts_format(self, mock_print):\n        # def test_0107__cliaccounts_format(self):\n        \"\"\"test cliaccounts_format one entry\"\"\"\n        result = [\n            {\n                \"id\": 1,\n                \"name\": \"name1\",\n                \"contact\": \"contact1\",\n                \"cliadmin\": True,\n                \"reportadmin\": True,\n                \"certificateadmin\": True,\n                \"created_at\": \"created_at1\",\n            }\n        ]\n        self.housekeeping._cliaccounts_format(result)\n        self.assertIn(\n            call(\n                \"\\nName           |Contact             |cliadm|repadm|certadm|Created at          \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"------------------------------------------------------------------------------\"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"name1          |contact1            |True  |True  |True   |created_at1         \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(call(\"\\n\"), mock_print.mock_calls)\n\n    @patch(\"builtins.print\")\n    def test_109__cliaccounts_format(self, mock_print):\n        # def test_0107__cliaccounts_format(self):\n        \"\"\"test cliaccounts_format two entries\"\"\"\n        result = [\n            {\n                \"id\": 1,\n                \"name\": \"name1\",\n                \"contact\": \"contact1\",\n                \"cliadmin\": True,\n                \"reportadmin\": True,\n                \"certificateadmin\": True,\n                \"created_at\": \"created_at1\",\n            },\n            {\n                \"id\": 2,\n                \"name\": \"name2\",\n                \"contact\": \"contact2\",\n                \"cliadmin\": True,\n                \"reportadmin\": True,\n                \"certificateadmin\": True,\n                \"created_at\": \"created_at2\",\n            },\n        ]\n        self.housekeeping._cliaccounts_format(result)\n        self.assertIn(\n            call(\n                \"\\nName           |Contact             |cliadm|repadm|certadm|Created at          \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"------------------------------------------------------------------------------\"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"name1          |contact1            |True  |True  |True   |created_at1         \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"name2          |contact2            |True  |True  |True   |created_at2         \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(call(\"\\n\"), mock_print.mock_calls)\n\n    @patch(\"builtins.print\")\n    def test_110__cliaccounts_format(self, mock_print):\n        # def test_0107__cliaccounts_format(self):\n        \"\"\"test cliaccounts_format two entries to be reordered\"\"\"\n        result = [\n            {\n                \"id\": 2,\n                \"name\": \"name2\",\n                \"contact\": \"contact2\",\n                \"cliadmin\": True,\n                \"reportadmin\": True,\n                \"certificateadmin\": True,\n                \"created_at\": \"created_at2\",\n            },\n            {\n                \"id\": 1,\n                \"name\": \"name1\",\n                \"contact\": \"contact1\",\n                \"cliadmin\": True,\n                \"reportadmin\": True,\n                \"certificateadmin\": True,\n                \"created_at\": \"created_at1\",\n            },\n        ]\n        self.housekeeping._cliaccounts_format(result)\n        self.assertIn(\n            call(\n                \"\\nName           |Contact             |cliadm|repadm|certadm|Created at          \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"------------------------------------------------------------------------------\"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"name1          |contact1            |True  |True  |True   |created_at1         \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(\n            call(\n                \"name2          |contact2            |True  |True  |True   |created_at2         \"\n            ),\n            mock_print.mock_calls,\n        )\n        self.assertIn(call(\"\\n\"), mock_print.mock_calls)\n\n    @patch(\"builtins.print\")\n    def test_111__cliaccounts_format(self, mock_print):\n        \"\"\"test cliaccounts_format two entries to be reordered\"\"\"\n        result = [\"string\"]\n\n        mock_print.side_effect = Exception(\"mock_print exception\")\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.housekeeping._cliaccounts_format(result)\n        self.assertIn(\n            \"ERROR:test_a2c:Error in when formating cliaccounts: mock_print exception\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_112_cli_usermgr(self, mock_chk):\n        \"\"\"test cli_usermgr with failed config check\"\"\"\n        config_dic = {}\n        mock_chk.return_value = False\n        self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_113_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr incomplete config\"\"\"\n        config_dic = {}\n        mock_build.return_value = {}\n        mock_chk.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:Error in CLI usermanagement: data incomplete\",\n            lcm.output,\n        )\n        self.assertFalse(self.housekeeping.dbstore.cliaccount_add.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_114_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr add\"\"\"\n        config_dic = {}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.return_value = \"add\"\n        self.assertEqual(\"add\", self.housekeeping.cli_usermgr(config_dic))\n        self.assertTrue(self.housekeeping.dbstore.cliaccount_add.called)\n        self.assertFalse(self.housekeeping.dbstore.cliaccount_delete.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_115_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr delete False\"\"\"\n        config_dic = {\"delete\": False}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.return_value = \"add\"\n        self.assertEqual(\"add\", self.housekeeping.cli_usermgr(config_dic))\n        self.assertFalse(self.housekeeping.dbstore.cliaccount_delete.called)\n        self.assertTrue(self.housekeeping.dbstore.cliaccount_add.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_116_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr delete\"\"\"\n        config_dic = {\"delete\": True}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.return_value = \"add\"\n        self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertTrue(self.housekeeping.dbstore.cliaccount_delete.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_117_cli_usermgr(self, mock_chk, mock_build, mock_list):\n        \"\"\"test cli_usermgr list true\"\"\"\n        config_dic = {\"list\": True}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.return_value = \"add\"\n        self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertTrue(mock_list.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_118_cli_usermgr(self, mock_chk, mock_build, mock_list):\n        \"\"\"test cli_usermgr list false\"\"\"\n        config_dic = {\"list\": False}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.return_value = \"add\"\n        self.assertEqual(\"add\", self.housekeeping.cli_usermgr(config_dic))\n        self.assertFalse(mock_list.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_119_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr exception in add\"\"\"\n        config_dic = {\"list\": False}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_add.side_effect = Exception(\"exc_add\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to manage CLI user: exc_add\",\n            lcm.output,\n        )\n        # self.assertFalse(self.housekeeping.dbstore.cliaccount_delete.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_120_cli_usermgr(self, mock_chk, mock_build):\n        \"\"\"test cli_usermgr exception in delete\"\"\"\n        config_dic = {\"delete\": True}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        self.housekeeping.dbstore.cliaccount_delete.side_effect = Exception(\n            \"exc_delete\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to manage CLI user: exc_delete\",\n            lcm.output,\n        )\n        # self.assertFalse(self.housekeeping.dbstore.cliaccount_add.called)\n\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliaccounts_list\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._data_dic_build\")\n    @patch(\"acme_srv.housekeeping.Housekeeping._cliconfig_check\")\n    def test_121_cli_usermgr(self, mock_chk, mock_build, mock_list):\n        \"\"\"test cli_usermgr exception in list\"\"\"\n        config_dic = {\"list\": True}\n        mock_build.return_value = {\"name\": \"name\"}\n        mock_chk.return_value = True\n        mock_list.side_effect = Exception(\"exc_list\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.housekeeping.cli_usermgr(config_dic))\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to manage CLI user: exc_list\",\n            lcm.output,\n        )\n        self.assertTrue(mock_list.called)\n\n    def test_122_data_dic_build(self):\n        \"\"\"test _data_dic_build() - empty dic\"\"\"\n        config_dic = {}\n        self.assertFalse(self.housekeeping._data_dic_build(config_dic))\n\n    def test_123_data_dic_build(self):\n        \"\"\"test _data_dic_build() - jwkname set\"\"\"\n        config_dic = {\"jwkname\": \"jwkname\"}\n        result_dic = {\"name\": \"jwkname\"}\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_124_data_dic_build(self):\n        \"\"\"test _data_dic_build() - kid set\"\"\"\n        config_dic = {\"jwk\": {\"kid\": \"kid\", \"foo\": \"bar\"}}\n        result_dic = {\"jwk\": '{\"kid\": \"kid\", \"foo\": \"bar\"}', \"name\": \"kid\"}\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_125_data_dic_build(self):\n        \"\"\"test _data_dic_build() - kid not set but other parameters\"\"\"\n        config_dic = {\"jwk\": {\"foo\": \"bar\"}, \"jwkname\": \"jwkname\"}\n        result_dic = {\"jwk\": '{\"foo\": \"bar\"}', \"name\": \"jwkname\"}\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_126_data_dic_build(self):\n        \"\"\"test _data_dic_build() - add email\"\"\"\n        config_dic = {\"jwk\": {\"foo\": \"bar\"}, \"jwkname\": \"jwkname\", \"email\": \"email\"}\n        result_dic = {\"jwk\": '{\"foo\": \"bar\"}', \"name\": \"jwkname\", \"contact\": \"email\"}\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_127_data_dic_build(self):\n        \"\"\"test _data_dic_build() - permissions set\"\"\"\n        config_dic = {\n            \"jwk\": {\"foo\": \"bar\"},\n            \"jwkname\": \"jwkname\",\n            \"email\": \"email\",\n            \"permissions\": {\"perm1\": \"perm1\"},\n        }\n        result_dic = {\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"name\": \"jwkname\",\n            \"contact\": \"email\",\n            \"perm1\": \"perm1\",\n        }\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_128_data_dic_build(self):\n        \"\"\"test _data_dic_build() - string\"\"\"\n        config_dic = {\n            \"jwk\": {\"foo\": \"bar\"},\n            \"jwkname\": \"jwkname\",\n            \"email\": \"email\",\n            \"permissions\": \"permissions\",\n        }\n        result_dic = {\"jwk\": '{\"foo\": \"bar\"}', \"name\": \"jwkname\", \"contact\": \"email\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n        self.assertIn(\n            \"ERROR:test_a2c:Error in while building the data dictionary: dictionary update sequence element #0 has length 1; 2 is required\",\n            lcm.output,\n        )\n\n    def test_129_data_dic_build(self):\n        \"\"\"test _data_dic_build() - delete false\"\"\"\n        config_dic = {\n            \"jwk\": {\"foo\": \"bar\"},\n            \"jwkname\": \"jwkname\",\n            \"email\": \"email\",\n            \"permissions\": {\"perm1\": \"perm1\"},\n            \"delete\": False,\n        }\n        result_dic = {\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"name\": \"jwkname\",\n            \"contact\": \"email\",\n            \"perm1\": \"perm1\",\n        }\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n    def test_130_data_dic_build(self):\n        \"\"\"test _data_dic_build() - delete true\"\"\"\n        config_dic = {\n            \"jwk\": {\"foo\": \"bar\"},\n            \"jwkname\": \"jwkname\",\n            \"email\": \"email\",\n            \"permissions\": {\"perm1\": \"perm1\"},\n            \"delete\": True,\n        }\n        result_dic = {\"name\": \"jwkname\"}\n        self.assertEqual(result_dic, self.housekeeping._data_dic_build(config_dic))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_message.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"unittests for message.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport importlib\nimport configparser\nfrom unittest.mock import patch, MagicMock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    @patch.dict(\"os.environ\", {\"ACME_SRV_CONFIGFILE\": \"ACME_SRV_CONFIGFILE\"})\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n\n    @patch(\"acme_srv.message.decode_message\")\n    def test_001_message_check_decoding_error(self, mock_decode):\n        \"\"\"message_check failed bcs of decoding error\"\"\"\n        message = '{\"foo\" : \"bar\"}'\n        mock_decode.return_value = (False, \"detail\", None, None, None)\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:malformed\", \"detail\", None, None, None),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_002_message_check_nonce_failed(self, mock_decode, mock_nonce_check):\n        \"\"\"message_check nonce check failed\"\"\"\n        message = '{\"foo\" : \"bar\"}'\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (400, \"badnonce\", None)\n        self.assertEqual(\n            (400, \"badnonce\", None, \"protected\", \"payload\", None),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_003_message_check_account_lookup_failed(\n        self, mock_decode, mock_nonce_check\n    ):\n        \"\"\"message check failed bcs account id lookup failed\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (200, None, None)\n        self.message.eabkid_check_disable = True\n        message = '{\"foo\" : \"bar\"}'\n        self.assertEqual(\n            (\n                403,\n                \"urn:ietf:params:acme:error:accountDoesNotExist\",\n                None,\n                \"protected\",\n                \"payload\",\n                None,\n            ),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_004_message_check_signature_failed(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk\n    ):\n        \"\"\"message check failed bcs signature_check_failed\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (200, None, None)\n        mock_aname.return_value = \"account_name\"\n        mock_eabchk.return_value = \"account_name\"\n        mock_sig.return_value = (False, \"error\", \"detail\")\n        message = '{\"foo\" : \"bar\"}'\n        self.assertEqual(\n            (403, \"error\", \"detail\", \"protected\", \"payload\", \"account_name\"),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_005_message_check_invalid_eab_credentials(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk\n    ):\n        \"\"\"message check failed bcs signature_check_failed\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (200, None, None)\n        mock_aname.return_value = \"account_name\"\n        mock_eabchk.return_value = None\n        mock_sig.return_value = (False, \"error\", \"detail\")\n        message = '{\"foo\" : \"bar\"}'\n        self.message.config.eabkid_check_disable = False\n        self.assertEqual(\n            (\n                403,\n                \"urn:ietf:params:acme:error:unauthorized\",\n                \"invalid eab credentials\",\n                \"protected\",\n                \"payload\",\n                None,\n            ),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_006_message_check_successful(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk\n    ):\n        \"\"\"message check successful\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (200, None, None)\n        mock_aname.return_value = \"account_name\"\n        mock_eabchk.return_value = \"account_name\"\n        mock_sig.return_value = (True, None, None)\n        message = '{\"foo\" : \"bar\"}'\n        self.assertEqual(\n            (200, None, None, \"protected\", \"payload\", \"account_name\"),\n            self.message.check(message),\n        )\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_007_message_check_nonce_disabled(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk\n    ):\n        \"\"\"message check successful as nonce check is disabled\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (400, \"badnonce\", None)\n        mock_aname.return_value = \"account_name\"\n        mock_sig.return_value = (True, None, None)\n        mock_eabchk.return_value = \"account_name\"\n        message = '{\"foo\" : \"bar\"}'\n        self.message.config.nonce_check_disable = True\n        self.message.config.signature_check_disable = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (200, None, None, \"protected\", \"payload\", \"account_name\"),\n                self.message.check(message, skip_nonce_check=True),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_008_message_check_signature_nonce_disabled(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk\n    ):\n        \"\"\"message check successful as nonce check is disabled\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (400, \"badnonce\", None)\n        mock_aname.return_value = \"account_name\"\n        mock_sig.return_value = (True, None, None)\n        self.message.config.eabkid_check_disable = True\n        self.message.config.nonce_check_disable = True\n        self.message.config.signature_check_disable = True\n        message = '{\"foo\" : \"bar\"}'\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (200, None, None, \"protected\", \"payload\", \"account_name\"),\n                self.message.check(message, skip_nonce_check=True),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:**** SIGNATURE_CHECK_DISABLE!!! Severe security issue ****\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****\",\n            lcm.output,\n        )\n        self.assertFalse(mock_eabchk.called)\n\n    @patch(\"acme_srv.message.Message._check_and_handle_invalid_eab_credentials\")\n    @patch(\"acme_srv.signature.Signature.check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.nonce.Nonce.check\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_009_message_check_nonce_disabled_keyrollover(\n        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eab_chk\n    ):\n        \"\"\"message check successful as nonce check is disabled\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_nonce_check.return_value = (400, \"badnonce\", None)\n        mock_aname.return_value = \"account_name\"\n        mock_sig.return_value = (True, None, None)\n        mock_eab_chk.return_value = \"account_name\"\n        message = '{\"foo\" : \"bar\"}'\n        self.message.config.nonce_check_disable = False\n        self.message.config.signature_check_disable = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (200, None, None, \"protected\", \"payload\", \"account_name\"),\n                self.message.check(message, skip_nonce_check=True),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Skip nonce check of inner payload during keyrollover\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_010_message_prepare_response_complete_data(self, mock_nnonce):\n        \"\"\"Message.prepare_respons for code 200 and complete data\"\"\"\n        data_dic = {\n            \"data\": {\"foo_data\": \"bar_bar\"},\n            \"header\": {\"foo_header\": \"bar_header\"},\n        }\n        mock_nnonce.return_value = \"new_nonce\"\n        config_dic = {\"code\": 200, \"message\": \"message\", \"detail\": \"detail\"}\n        self.assertEqual(\n            {\n                \"header\": {\"foo_header\": \"bar_header\", \"Replay-Nonce\": \"new_nonce\"},\n                \"code\": 200,\n                \"data\": {\"foo_data\": \"bar_bar\"},\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    @patch(\"acme_srv.error.Error.enrich_error\")\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_011_message_prepare_response_no_header(self, mock_nnonce, mock_error):\n        \"\"\"Message.prepare_respons for code 200 without header tag in response_dic\"\"\"\n        data_dic = {\n            \"data\": {\"foo_data\": \"bar_bar\"},\n        }\n        mock_nnonce.return_value = \"new_nonce\"\n        mock_error.return_value = \"mock_error\"\n        config_dic = {\"code\": 200, \"message\": \"message\", \"detail\": \"detail\"}\n        self.assertEqual(\n            {\n                \"header\": {\"Replay-Nonce\": \"new_nonce\"},\n                \"code\": 200,\n                \"data\": {\"foo_data\": \"bar_bar\"},\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_012_message_prepare_response_no_code(self, mock_nnonce):\n        \"\"\"Message.prepare_response for config_dic without code key\"\"\"\n        data_dic = {\n            \"data\": {\"foo_data\": \"bar_bar\"},\n            \"header\": {\"foo_header\": \"bar_header\"},\n        }\n        mock_nnonce.return_value = \"new_nonce\"\n        # mock_error.return_value = 'mock_error'\n        config_dic = {\"message\": \"type\", \"detail\": \"detail\"}\n        self.assertEqual(\n            {\n                \"header\": {\"Replay-Nonce\": \"new_nonce\", \"foo_header\": \"bar_header\"},\n                \"code\": 500,\n                \"data\": {\n                    \"detail\": \"http status code missing\",\n                    \"type\": \"urn:ietf:params:acme:error:serverInternal\",\n                    \"status\": 500,\n                },\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_013_message_prepare_response_no_message(self, mock_nnonce):\n        \"\"\"Message.prepare_response for config_dic without message key\"\"\"\n        data_dic = {\n            \"data\": {\"foo_data\": \"bar_bar\"},\n            \"header\": {\"foo_header\": \"bar_header\"},\n        }\n        mock_nnonce.return_value = \"new_nonce\"\n        # mock_error.return_value = 'mock_error'\n        config_dic = {\"code\": 400, \"detail\": \"detail\"}\n        self.assertEqual(\n            {\n                \"header\": {\"Replay-Nonce\": \"new_nonce\", \"foo_header\": \"bar_header\"},\n                \"code\": 400,\n                \"data\": {\n                    \"detail\": \"detail\",\n                    \"type\": \"urn:ietf:params:acme:error:serverInternal\",\n                    \"status\": 400,\n                },\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_014_message_prepare_response_no_detail(self, mock_nnonce):\n        \"\"\"Message.repare_response for config_dic without detail key\"\"\"\n        data_dic = {\n            \"data\": {\"foo_data\": \"bar_bar\"},\n            \"header\": {\"foo_header\": \"bar_header\"},\n        }\n        mock_nnonce.return_value = \"new_nonce\"\n        config_dic = {\"code\": 400, \"type\": \"message\"}\n        self.assertEqual(\n            {\n                \"header\": {\"Replay-Nonce\": \"new_nonce\", \"foo_header\": \"bar_header\"},\n                \"code\": 400,\n                \"data\": {\"type\": \"message\", \"status\": 400},\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    @patch(\"acme_srv.error.Error.enrich_error\")\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_015_message_prepare_response_no_data(self, mock_nnonce, mock_error):\n        \"\"\"Message.prepare_response for response_dic without data key\"\"\"\n        data_dic = {\"header\": {\"foo_header\": \"bar_header\"}}\n        mock_nnonce.return_value = \"new_nonce\"\n        mock_error.return_value = \"mock_error\"\n        config_dic = {\"code\": 400, \"type\": \"message\", \"detail\": \"detail\"}\n        self.assertEqual(\n            {\n                \"header\": {\"Replay-Nonce\": \"new_nonce\", \"foo_header\": \"bar_header\"},\n                \"code\": 400,\n                \"data\": {\"detail\": \"mock_error\", \"type\": \"message\", \"status\": 400},\n            },\n            self.message.prepare_response(data_dic, config_dic),\n        )\n\n    def test_016_message_name_get_empty_content(self):\n        \"\"\"test Message.name_get() with empty content\"\"\"\n        protected = {}\n        self.assertFalse(self.message._extract_account_name_from_content(protected))\n\n    def test_017_message_name_get_kid_nonsense(self):\n        \"\"\"test Message.name_get() with kid with nonsens in content\"\"\"\n        protected = {\"kid\": \"foo\"}\n        self.assertEqual(\n            \"foo\", self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_018_message_name_get_wrong_kid(self):\n        \"\"\"test Message.name_get() with wrong kid in content\"\"\"\n        protected = {\"kid\": \"http://tester.local/acme/account/account_name\"}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_019_message_name_get_correct_kid(self):\n        \"\"\"test Message.name_get() with correct kid in content\"\"\"\n        protected = {\"kid\": \"http://tester.local/acme/acct/account_name\"}\n        self.assertEqual(\n            \"account_name\", self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_020_message_name_get_jwk_no_url(self):\n        \"\"\"test Message.name_get() with 'jwk' in content but without URL\"\"\"\n        protected = {\"jwk\": \"jwk\"}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_021_message_name_get_jwk_wrong_url(self):\n        \"\"\"test Message.name_get() with 'jwk' and 'url' in content but url is wrong\"\"\"\n        protected = {\"jwk\": \"jwk\", \"url\": \"url\"}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_022_message__name_get(self):\n        \"\"\"test Message.name_get() with 'jwk' and correct 'url' in content but no 'n' in jwk\"\"\"\n        protected = {\"jwk\": \"jwk\", \"url\": \"http://tester.local/acme/revokecert\"}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_023_message__name_get(self):\n        \"\"\"test Message.name_get() with 'jwk' and correct 'url' but account lookup failed\"\"\"\n        protected = {\"jwk\": {\"n\": \"n\"}, \"url\": \"http://tester.local/acme/revokecert\"}\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.return_value = {}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_024_message__name_get(self):\n        \"\"\"test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data\"\"\"\n        protected = {\"jwk\": {\"n\": \"n\"}, \"url\": \"http://tester.local/acme/revokecert\"}\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.return_value = {\"bar\": \"foo\"}\n        self.assertEqual(\n            None, self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_025_message__name_get(self):\n        \"\"\"test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data\"\"\"\n        protected = {\"jwk\": {\"n\": \"n\"}, \"url\": \"http://tester.local/acme/revokecert\"}\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.return_value = {\"name\": \"foo\"}\n        self.assertEqual(\n            \"foo\", self.message._extract_account_name_from_content(protected)\n        )\n\n    def test_026_message__name_get(self):\n        \"\"\"test Message.name_get() - dbstore.account_lookup raises an exception\"\"\"\n        protected = {\"jwk\": {\"n\": \"n\"}, \"url\": \"http://tester.local/acme/revokecert\"}\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = Exception(\"exc_mess__name_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.message._extract_account_name_from_content(protected)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up account name for revocation: exc_mess__name_get\",\n            lcm.output,\n        )\n\n    def test_027__enter__(self):\n        \"\"\"test enter\"\"\"\n        self.message.__enter__()\n\n    @patch(\"acme_srv.message.load_config\")\n    def test_028_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['Account'] = {'foo': 'bar'}\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.load_config\")\n    def test_029_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Nonce\"] = {\n            \"nonce_check_disable\": False,\n            \"signature_check_disable\": False,\n        }\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.load_config\")\n    def test_030_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Nonce\"] = {\n            \"nonce_check_disable\": True,\n            \"signature_check_disable\": False,\n        }\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertTrue(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.load_config\")\n    def test_031_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Nonce\"] = {\n            \"nonce_check_disable\": False,\n            \"signature_check_disable\": True,\n        }\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertTrue(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.load_config\")\n    def test_032_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Directory\"] = {\"url_prefix\": \"url_prefix\", \"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertEqual(\"url_prefix/acme/acct/\", self.message.config.acct_path)\n        self.assertEqual(\n            \"url_prefix/acme/revokecert\", self.message.config.revocation_path\n        )\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_033_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load explicit false in cfg\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"eab_handler_file\": \"eab_handler_file\",\n            \"eabkid_check_disable\": False,\n        }\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = MagicMock()\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        # self.message._config_load()\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertFalse(self.message.config.eabkid_check_disable)\n        self.assertTrue(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_034_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"eab_handler_file\": \"eab_handler_file\",\n            \"eabkid_check_disable\": True,\n        }\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_035_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\", \"eabkid_check_disable\": True}\n        mock_load_cfg.return_value = parser\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_036_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load wrong eab handler config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"foo\": \"bar\", \"invalid_eabkid_deactivate\": True}\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.message._load_configuration()\n        self.assertIn(\n            \"CRITICAL:test_a2c:EABHandler configuration incomplete\",\n            lcm.output,\n        )\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_136_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAHandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = None\n        self.message._load_configuration()\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_037_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"eab_handler_file\": \"eab_handler_file\",\n            \"eabkid_check_disable\": True,\n        }\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = None\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        # self.assertIn('CRITICAL:test_a2c:Account._config_load(): EABHandler could not get loaded', lcm.output)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_038_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load eab_load returned None\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\"eab_handler_file\": \"eab_handler_file\"}\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = None\n        from acme_srv.message import Message\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n\n            self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertIn(\n            \"CRITICAL:test_a2c:EABHandler could not get loaded\",\n            lcm.output,\n        )\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertFalse(self.message.config.eabkid_check_disable)\n        self.assertTrue(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_039_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"eab_handler_file\": \"eab_handler_file\",\n            \"invalid_eabkid_deactivate\": True,\n        }\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = MagicMock()\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        from acme_srv.message import Message\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertFalse(self.message.config.eabkid_check_disable)\n        self.assertTrue(mock_eab.called)\n        self.assertTrue(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.eab_handler_load\")\n    @patch(\"acme_srv.message.load_config\")\n    def test_040_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"test _config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"EABhandler\"] = {\n            \"eab_handler_file\": \"eab_handler_file\",\n            \"invalid_eabkid_deactivate\": True,\n            \"eabkid_check_disable\": True,\n        }\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = None\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.message._load_configuration()\n        # self.assertIn('CRITICAL:test_a2c:Account._config_load(): EABHandler could not get loaded', lcm.output)\n        self.assertFalse(self.message.config.nonce_check_disable)\n        self.assertFalse(self.message.config.signature_check_disable)\n        self.assertTrue(self.message.config.eabkid_check_disable)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(self.message.config.invalid_eabkid_deactivate)\n\n    @patch(\"acme_srv.message.decode_message\")\n    def test_041_message_check(self, mock_decode):\n        \"\"\"cli_check failed bcs of decoding error\"\"\"\n        message = '{\"foo\" : \"bar\"}'\n        mock_decode.return_value = (False, \"detail\", None, None, None)\n        self.assertEqual(\n            (\n                400,\n                \"urn:ietf:params:acme:error:malformed\",\n                \"detail\",\n                None,\n                None,\n                None,\n                {},\n            ),\n            self.message.cli_check(message),\n        )\n\n    @patch(\"acme_srv.signature.Signature.cli_check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_042_message_check(self, mock_decode, mock_name_get, mock_check):\n        \"\"\"message check failed bcs sig.cli_check() failed\"\"\"\n        self.message.dbstore = MagicMock()\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_check.return_value = (False, \"error\", \"detail\")\n        mock_name_get.return_value = \"name\"\n        message = '{\"foo\" : \"bar\"}'\n        self.assertEqual(\n            (403, \"error\", \"detail\", \"protected\", \"payload\", \"name\", {}),\n            self.message.cli_check(message),\n        )\n        self.assertFalse(self.message.dbstore.cli_permissions_get.called)\n\n    @patch(\"acme_srv.signature.Signature.cli_check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_043_message_check(self, mock_decode, mock_name_get, mock_check):\n        \"\"\"message check failed bcs sig.cli_check() successful\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_check.return_value = (\"True\", \"error\", \"detail\")\n        self.message.repo = MagicMock()\n        self.message.repo.cli_permissions_get.return_value = {\"foo\": \"bar\"}\n        mock_name_get.return_value = \"name\"\n        message = '{\"foo\" : \"bar\"}'\n        self.assertEqual(\n            (200, None, None, \"protected\", \"payload\", \"name\", {\"foo\": \"bar\"}),\n            self.message.cli_check(message),\n        )\n\n    @patch(\"acme_srv.signature.Signature.cli_check\")\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    @patch(\"acme_srv.message.decode_message\")\n    def test_044_message_check(self, mock_decode, mock_name_get, mock_check):\n        \"\"\"message check failed bcs sig.cli_check() successful\"\"\"\n        mock_decode.return_value = (True, None, \"protected\", \"payload\", \"signature\")\n        mock_check.return_value = (\"True\", \"error\", \"detail\")\n        self.message.repo = MagicMock()\n        self.message.repo.cli_permissions_get.side_effect = Exception(\"db error\")\n        mock_name_get.return_value = \"name\"\n        message = '{\"foo\" : \"bar\"}'\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (200, None, None, \"protected\", \"payload\", \"name\", {}),\n                self.message.cli_check(message),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:cli_permissions_get failed: db error\",\n            lcm.output,\n        )\n\n    def test_044_invalid_eab_check(self):\n        \"\"\"test _invalid_eab_check - ok\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = None\n        self.message.repo.account_lookup.return_value = {\"eab_kid\": \"eab_kid\"}\n        eab_handler_module = importlib.import_module(\n            \"examples.eab_handler.skeleton_eab_handler\"\n        )\n        self.message.config.eab_handler = eab_handler_module.EABhandler\n        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=\"mac_key\")\n        self.assertEqual(\n            \"account_name\",\n            self.message._check_and_handle_invalid_eab_credentials(\"account_name\"),\n        )\n\n    def test_045_invalid_eab_check(self):\n        \"\"\"test _invalid_eab_check - ok\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = None\n        self.message.repo.account_lookup.return_value = {\"eab_kid\": \"eab_kid\"}\n        eab_handler_module = importlib.import_module(\n            \"examples.eab_handler.skeleton_eab_handler\"\n        )\n        self.message.config.eab_handler = eab_handler_module.EABhandler\n        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.message._check_and_handle_invalid_eab_credentials(\"account_name\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.\",\n            lcm.output,\n        )\n\n    def test_046_invalid_eab_check(self):\n        \"\"\"test _invalid_eab_check - ok\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = None\n        self.message.repo.account_update.side_effect = None\n        self.message.repo.account_lookup.return_value = {\"eab_kid\": \"eab_kid\"}\n        eab_handler_module = importlib.import_module(\n            \"examples.eab_handler.skeleton_eab_handler\"\n        )\n        self.message.config.eab_handler = eab_handler_module.EABhandler\n        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)\n        self.message.config.invalid_eabkid_deactivate = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.message._check_and_handle_invalid_eab_credentials(\"account_name\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Account account_name will be deactivated due to missing eab credentials\",\n            lcm.output,\n        )\n        self.assertTrue(self.message.repo.account_update.called)\n\n    def test_047_invalid_eab_check(self):\n        \"\"\"test _invalid_eab_check - ok\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = None\n        self.message.repo.account_lookup.return_value = {\"foo\": \"bar\"}\n        eab_handler_module = importlib.import_module(\n            \"examples.eab_handler.skeleton_eab_handler\"\n        )\n        self.message.config.eab_handler = eab_handler_module.EABhandler\n        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.message._check_and_handle_invalid_eab_credentials(\"account_name\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Account account_name has no eab credentials\", lcm.output\n        )\n\n    def test_048_invalid_eab_check(self):\n        \"\"\"test _invalid_eab_check - ok\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = None\n        self.message.repo.account_lookup.return_value = None\n        eab_handler_module = importlib.import_module(\n            \"examples.eab_handler.skeleton_eab_handler\"\n        )\n        self.message.config.eab_handler = eab_handler_module.EABhandler\n        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.message._check_and_handle_invalid_eab_credentials(\"account_name\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Account lookup for account_name failed.\", lcm.output\n        )\n\n    def test_049__safe_account_lookup_db_error(self):\n        \"\"\"test _safe_account_lookup - dbstore.account_lookup raises an exception\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_lookup.side_effect = Exception(\n            \"exc_safe_account_lookup\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.message._safe_account_lookup(\"account_name\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Account lookup for account_name failed: exc_safe_account_lookup\",\n            lcm.output,\n        )\n\n    def test_050_eab_mac_key_exists_exception(self):\n        \"\"\"Test _eab_mac_key_exists handles exception and returns False\"\"\"\n        self.message.config.eab_handler = MagicMock()\n        # Simulate eab_handler raising an exception when called\n        self.message.config.eab_handler.side_effect = Exception(\"handler error\")\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            result = self.message._eab_mac_key_exists(\"dummy_kid\")\n        self.assertFalse(result)\n        self.assertIn(\"ERROR:test_a2c:EAB handler error: handler error\", lcm.output)\n\n    def test_051_handle_missing_eab_credentials_db_error(self):\n        \"\"\"Test _handle_missing_eab_credentials handles db error gracefully\"\"\"\n        self.message.repo = MagicMock()\n        self.message.repo.account_update.side_effect = Exception(\"db error\")\n        self.message.config.invalid_eabkid_deactivate = True\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as lcm:\n            self.message._handle_missing_eab_credentials(\"dummy_account\", \"eab_kid\")\n        self.assertIn(\n            \"ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Account dummy_account will be deactivated due to missing eab credentials\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Account update failed: db error\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.message.Message._extract_account_name_from_content\")\n    def test_052_extract_account_name_from_content(self, mock_name_get):\n        \"\"\"Test _extract_account_name_from_content handles unexpected exceptions gracefully\"\"\"\n        mock_name_get.return_value = \"account_name\"\n        protected = {\"jwk\": {\"n\": \"n\"}, \"url\": \"http://tester.local/acme/revokecert\"}\n        self.assertEqual(\n            \"account_name\", self.message.extract_account_name_from_content(protected)\n        )\n\n\nclass TestAccountRepository(unittest.TestCase):\n    \"\"\"Unit tests for AccountRepository class\"\"\"\n\n    def setUp(self):\n        self.mock_dbstore = MagicMock()\n        self.repo = importlib.import_module(\"acme_srv.message\").AccountRepository(\n            self.mock_dbstore\n        )\n\n    def test_account_lookup_calls_dbstore(self):\n        self.mock_dbstore.account_lookup.return_value = \"result\"\n        result = self.repo.account_lookup(\"key\", \"value\")\n        self.mock_dbstore.account_lookup.assert_called_once_with(\"key\", \"value\")\n        self.assertEqual(result, \"result\")\n\n    def test_account_update_calls_dbstore(self):\n        self.mock_dbstore.account_update.return_value = \"updated\"\n        result = self.repo.account_update({\"foo\": \"bar\"}, True)\n        self.mock_dbstore.account_update.assert_called_once_with({\"foo\": \"bar\"}, True)\n        self.assertEqual(result, \"updated\")\n\n    def test_cli_permissions_get_calls_dbstore(self):\n        self.mock_dbstore.cli_permissions_get.return_value = {\"perm\": True}\n        result = self.repo.cli_permissions_get(\"account_name\")\n        self.mock_dbstore.cli_permissions_get.assert_called_once_with(\"account_name\")\n        self.assertEqual(result, {\"perm\": True})\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_msca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, Mock, MagicMock\nimport base64\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.ca_handler.mscertsrv_ca_handler import CAhandler\n\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem default output\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        with open(self.dir_path + \"/ca/certs.pem\", \"r\") as fso:\n            result = fso.read()\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content))\n\n    def test_003__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output string\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        with open(self.dir_path + \"/ca/certs.pem\", \"r\") as fso:\n            result = fso.read()\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"string\"))\n\n    def test_004__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        result = [\n            \"-----BEGIN CERTIFICATE-----\\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\\njX1vlY35Ofonc4+6dRVamBiF9A==\\n-----END CERTIFICATE-----\\n\",\n            \"-----BEGIN CERTIFICATE-----\\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\\nfvkG2Q==\\n-----END CERTIFICATE-----\\n\",\n        ]\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"list\"))\n\n    def test_005__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n        with open(self.dir_path + \"/ca/certs.p7b\", \"r\") as fso:\n            file_content = fso.read()\n        result = None\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"unknown\"))\n\n    def test_006__pkcs7_to_pem(self):\n        \"\"\"test pkcs7 to pem output list\"\"\"\n\n        file_content = base64.b64decode(\n            \"MIIK9AYJKoZIhvcNAQcCoIIK5TCCCuECAQExADALBgkqhkiG9w0BBwGgggrHMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1NDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEPMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8jqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/qkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT//WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCVXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9hcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLBZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB15Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilMGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8hH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dmKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYDVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77WvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP96YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqHJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwCzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ32tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/M7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5Z3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsFzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4tjX1vlY35Ofonc4+6dRVamBiF9DCCBXAwggNYoAMCAQICCHry008TjKGYMA0GCSqGSIb3DQEBCwUAMCsxFzAVBgNVBAsTDmFjbWUyY2VydGlmaWVyMRAwDgYDVQQDEwdyb290LWNhMB4XDTIwMDUyNzAwMDAwMFoXDTMwMDUyNjIzNTk1OVowKzEXMBUGA1UECxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCcuFGR3WYGLeuJP6xWpqAuu+rWL7Wm3roqlcNXOqFWSPPe3BSxugWMMq9hGo+7Ra6kyQ3jDeL2UrnS7Jiw6upvCsF/64j81EyJXIzOWiDADWa/ayxLNzVrXIr6JQeWkNbJYXVYrVDy7sbBZ2HkE8sRcj+5Z4PTP2eNNyixvKYXZiozNLyZGo+Drijl391LFqlkGMkZf5rNO8VY9NrqtPC5KHjvo7UIrL8lV1EXWgnHmbciv2QUOzRQrGytddnFUdXtmiaJezSQAOlpuogcwHZAANpd5IeNEi6BG2omlTIsSzdr4pSGTjgKA11+Pk+oq/ipw1UidsruXPziTMLl8B64ey4INb7BUeUzXoZJ4Y1LljlDvtE5Cj4NgOyk4O9jmdpjnC2SG8c69T+UUb3Zi0Cz60xdhCb6UDzZm16jd2VVhL3x045JExWP3bDk7xU4Eq4tec2CnIfL6LXFO8/gUIYJjLcDtiYTzJmegAXfbJCO4o1qcDpbQIcbXaATuk+ggQqxNsl3Olfz8sgCnBYJTZiIIbeaF7JxPrm/3bcfH/SHmv8TT3aOWhsvH3WoraJQytepHdym+zhOBzByMDscRdQRAKnq8cYWyzEQa/IUUmSOXLy81i76QEOc7oYw+ld2/QWBXeLowLt85d5m83W2IxaBjl+mgWWhg8ZXODtlux8Z7QIDAQABo4GXMIGUMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAxlb7kxWGbRAJM92n/cESgw7wLMMB8GA1UdIwQYMBaAFAxlb7kxWGbRAJM92n/cESgw7wLMMA4GA1UdDwEB/wQEAwIBhjARBglghkgBhvhCAQEEBAMCAAcwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAgEAI5KO3V/ogoE/ptyMtVYOo+zEXrzwM6tZah0UTTXbdnLed9KSLrexb+VdsWJN+ZGvovxvl8jr012vbwFCogbYkZ1D7F7uFHEuwnKwlxMx8eHjrR56ecA4TtBcefzlGU2j/i+z3dRg/4/ed4m8eWzGvzPUY/kuPOp7Lee7bg0ZhAGrxQ4jHei94x+GEnJI5iB9rkngyGkWvZOmoNO+15lob1WEPbbke0Rm0rrldxXOqanBW21qaC4TUXBXPoW1CjrCpdnD/kBCj/x9rN1jJZaoACimXjvtSjrBqNQg7OO+WyF8ggaRWx9hzj2rZrt8mUUPX4/bWvRFuOp99wxUTS4pxVmuFibOMi1T9Y7oHtkwjsgNbetYsvvkUV0ht3uzRWKxGbgobdRHQwHcyFGPPZvTQj8KA2EuAmoYJ35JDu2EHun+sqiuorMn/GRXKdbhefHhEQ4hfxQ6kvJy5gnVEbiOegw6Dbw6YoOSf/UTVhvLxL7dqe0K43mdxAGrceSbmLcvzSkx2cCVpOdQoMxIMw8MxNiSIqnpXbS+XdEpenlbr9BtOARMrl8RFqYFVcwUuKhBSAUp0yc4LtT15iHd8i74Nja/DSr8MmZjecShadAPQVqNec1tT1w2g/pd/aE2A+2oI15M0wI2CHHzSewEyGBQ9MQLzQDn+9LO2jbqRX75BtmhADEA\"\n        )\n        result = [\n            \"-----BEGIN CERTIFICATE-----\\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\\njX1vlY35Ofonc4+6dRVamBiF9A==\\n-----END CERTIFICATE-----\\n\",\n            \"-----BEGIN CERTIFICATE-----\\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\\nfvkG2Q==\\n-----END CERTIFICATE-----\\n\",\n        ]\n        self.assertEqual(result, self.cahandler._pkcs7_to_pem(file_content, \"list\"))\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_007_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['CAhandler'] = {'foo': 'bar'}\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_008_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with unknown values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_009_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section with host value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host\": \"host\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"host\", self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_010_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with user values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user\": \"user\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(\"user\", self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_011_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with password values\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password\": \"password\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertEqual(\"password\", self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_012_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with authmethod basic\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"auth_method\": \"basic\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_013_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with authmethod ntlm\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"auth_method\": \"ntlm\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"ntlm\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with authmethod unknown\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"auth_method\": \"unknown\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with ca_bundle value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with template value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"template\": \"template\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertEqual(\"template\", self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load cahandler section with template value\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"krb5_config\": \"krb5_config\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertEqual(\"krb5_config\", self.cahandler.krb5_config)\n\n    @patch.dict(\"os.environ\", {\"host_variable\": \"host\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with host variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"host_variable\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"host\", self.cahandler.host)\n\n    @patch.dict(\"os.environ\", {\"host_variable\": \"host\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with host variable which does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"doesnotexist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load host_variable from environment: 'doesnotexist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"host_variable\": \"host\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with host variable which gets overwritten\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"host_variable\", \"host\": \"host_local\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"host_local\", self.cahandler.host)\n        self.assertIn(\"INFO:test_a2c:Overwrite host\", lcm.output)\n\n    @patch.dict(\"os.environ\", {\"user_variable\": \"user\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"user_variable\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"user\", self.cahandler.user)\n\n    @patch.dict(\"os.environ\", {\"user_variable\": \"user\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with user variable which does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"doesnotexist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.user)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load user_variable from environment: 'doesnotexist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"user_variable\": \"user\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with user variable which gets overwritten\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"user_variable\", \"user\": \"user_local\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"user_local\", self.cahandler.user)\n        self.assertIn(\"INFO:test_a2c:Overwrite user\", lcm.output)\n\n    @patch.dict(\"os.environ\", {\"password_variable\": \"password\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with password variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password_variable\": \"password_variable\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"password\", self.cahandler.password)\n\n    @patch.dict(\"os.environ\", {\"password_variable\": \"password\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_025_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with password variable which does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password_variable\": \"doesnotexist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.password)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load password_variable from environment: 'doesnotexist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"password_variable\": \"password\"})\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_026_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load with password variable which gets overwritten\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"password_variable\": \"password_variable\",\n            \"password\": \"password_local\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"password_local\", self.cahandler.password)\n        self.assertIn(\"INFO:test_a2c:Overwrite password\", lcm.output)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.proxy_check\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_027_config_load(self, mock_load_cfg, mock_json, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_chk.called)\n        self.assertEqual(\n            {\"http\": \"proxy.bar.local\", \"https\": \"proxy.bar.local\"},\n            self.cahandler.proxy,\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.proxy_check\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_028_config_load(self, mock_load_cfg, mock_json, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies failed with exception in json.load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_json.side_effect = Exception(\"exc_load_config\")\n        mock_load_cfg.return_value = parser\n        mock_chk.side = \"proxy.bar.local\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertFalse(mock_chk.called)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load proxy_server_list from configuration: exc_load_config\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.config_eab_profile_load\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.load_config\")\n    def test_029_config_load(self, mock_load_cfg, mock_eab):\n        \"\"\"allowd_domain_list\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"foo\": \"bar\",\n            \"eab_handler\": \"handler\",\n            \"eab_profiling\": \"eab\",\n        }\n        mock_load_cfg.return_value = parser\n        mock_eab.return_value = [\"eab\", \"handler\"]\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"basic\", self.cahandler.auth_method)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.krb5_config)\n        self.assertEqual(\"handler\", self.cahandler.eab_handler)\n        self.assertEqual(\"eab\", self.cahandler.eab_profiling)\n\n    def test_030_revoke(self):\n        \"\"\"test revocation\"\"\"\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Revocation is not supported.\",\n            ),\n            self.cahandler.revoke(\"cert\", \"rev_reason\", \"rev_date\"),\n        )\n\n    def test_031_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_032_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_033_check_credentials(self):\n        \"\"\"test polling\"\"\"\n        ca_server = Mock()\n        ca_server.check_credentials = Mock(return_value=True)\n        self.assertTrue(self.cahandler._check_credentials(ca_server))\n\n    def test_034_check_credentials(self):\n        \"\"\"test polling\"\"\"\n        ca_server = Mock()\n        ca_server.check_credentials = Mock(return_value=False)\n        self.assertFalse(self.cahandler._check_credentials(ca_server))\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._config_load\")\n    def test_035__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    def test_036_enroll(self):\n        \"\"\"enroll without having self.host\"\"\"\n        self.assertEqual(\n            (\"Config incomplete\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    def test_037_enroll(self):\n        \"\"\"enroll without having self.user\"\"\"\n        self.cahandler.host = \"host\"\n        self.assertEqual(\n            (\"Config incomplete\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    def test_038_enroll(self):\n        \"\"\"enroll without having self.password\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.assertEqual(\n            (\"Config incomplete\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    def test_039_enroll(self):\n        \"\"\"enroll without having self.template\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.assertEqual(\n            (\"Config incomplete\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"certsrv.Certsrv\")\n    def test_040_enroll(self, mock_certserver, mock_credchk):\n        \"\"\"enroll credential check failed\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_certserver.return_value = \"foo\"\n        mock_credchk.return_value = False\n        self.assertEqual(\n            (\"Connection or Credentialcheck failed.\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._template_name_get\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_041_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p, mock_tmpl\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        self.assertEqual(\n            (None, \"get_certp2p\", \"get_cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertFalse(mock_tmpl.called)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._template_name_get\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_042_enroll(\n        self,\n        mock_certserver,\n        mock_credchk,\n        mockwrap,\n        mock_b2s,\n        mock_p2p,\n        mock_tmpl,\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        self.assertEqual(\n            (None, \"get_certp2p\", \"get_cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertFalse(mock_tmpl.called)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._template_name_get\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_043_enroll(\n        self,\n        mock_certserver,\n        mock_credchk,\n        mockwrap,\n        mock_b2s,\n        mock_p2p,\n        mock_tmpl,\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        self.assertEqual(\n            (None, \"get_certp2p\", \"get_cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertFalse(mock_tmpl.called)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_044_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p, mock_eab\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.header_info_field = \"header_info\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_eab.return_value = False\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        self.assertEqual(\n            (None, \"get_certp2p\", \"get_cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_eab.called)\n        self.assertEqual(\"template\", self.cahandler.template)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_045_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p, mock_eab\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.header_info_field = \"header_info\"\n        self.cahandler.krb5_config = \"krb5_config\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_eab.return_value = \"error\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"INFO:test_a2c:Load krb5config from krb5_config\",\n            lcm.output,\n        )\n        self.assertTrue(mock_eab.called)\n        self.assertEqual(\"template\", self.cahandler.template)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_046_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p, mock_eab\n    ):\n        \"\"\"enroll enroll successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.header_info_field = \"header_info\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_eab.return_value = None\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        self.assertEqual(\n            (None, \"get_certp2p\", \"get_cert\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_eab.called)\n        self.assertEqual(\"template\", self.cahandler.template)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_047_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p\n    ):\n        \"\"\"enroll exceütption in get chain\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [Exception(\"exc_get_chain\"), \"get_cert\"]\n        mock_p2p.return_value = \"p2p\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\n                    \"Certificate bundling failed: missing CA certificate or issued certificate.\",\n                    None,\n                    \"get_cert\",\n                    None,\n                ),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to get CA certificate chain: exc_get_chain\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to bundle certificates: missing ca_pem or cert_raw.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._pkcs7_to_pem\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.convert_byte_to_string\")\n    @patch(\"textwrap.fill\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.Certsrv\")\n    def test_048_enroll(\n        self, mock_certserver, mock_credchk, mockwrap, mock_b2s, mock_p2p\n    ):\n        \"\"\"enroll exceütption in get cert\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mockresponse = MagicMock()\n        mockresponse.get_chain.return_value = \"get_chain\"\n        mockresponse.get_cert.return_value = \"get_cert\"\n        mock_certserver = mockresponse\n        mock_credchk.return_value = True\n        mockwrap.return_value = \"mockwrap\"\n        mock_b2s.side_effect = [\"get_chain\", Exception(\"get_cert\")]\n        mock_p2p.return_value = \"p2p\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"get_cert\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to enroll certificate from CA: get_cert\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"certsrv.Certsrv\")\n    def test_049_enroll(self, mock_certserver, mock_credchk, mock_ecl):\n        \"\"\"enroll credential check failed\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_certserver.return_value = \"foo\"\n        mock_credchk.return_value = False\n        self.assertEqual(\n            (\"Connection or Credentialcheck failed.\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.CAhandler._check_credentials\")\n    @patch(\"certsrv.Certsrv\")\n    def test_050_enroll(self, mock_certserver, mock_credchk, mock_ecl):\n        \"\"\"enroll credential check failed\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.enrollment_config_log = True\n        mock_certserver.return_value = \"foo\"\n        mock_credchk.return_value = False\n        self.assertEqual(\n            (\"Connection or Credentialcheck failed.\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.header_info_get\")\n    def test_051_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [\n            {\n                \"header_info\": '{\"header_field\": \"template=foo lego-cli/4.14.2 xenolf-acme/4.14.2 (release; linux; amd64)\"}'\n            }\n        ]\n        self.cahandler.header_info_field = \"header_field\"\n        self.assertEqual(\"foo\", self.cahandler._template_name_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.header_info_get\")\n    def test_052_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [\n            {\n                \"header_info\": '{\"header_field\": \"Template=foo lego-cli/4.14.2 xenolf-acme/4.14.2 (release; linux; amd64)\"}'\n            }\n        ]\n        self.cahandler.header_info_field = \"header_field\"\n        self.assertEqual(\"foo\", self.cahandler._template_name_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.header_info_get\")\n    def test_053_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [{\"header_info\": \"header_info\"}]\n        self.cahandler.header_info_field = \"header_field\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._template_name_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse template from header_info: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_054_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\", \"bar\", \"foobar\"]'}}\n        self.cahandler._config_headerinfo_load(config_dic)\n        self.assertEqual(\"foo\", self.cahandler.header_info_field)\n\n    def test_055_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\"]'}}\n        self.cahandler._config_headerinfo_load(config_dic)\n        self.assertEqual(\"foo\", self.cahandler.header_info_field)\n\n    def test_056_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": \"foo\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_headerinfo_load(config_dic)\n        self.assertFalse(self.cahandler.header_info_field)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse header_info_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_057__config_url_load(self):\n        \"\"\"test _config_url_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"url\": \"foo\"}\n        self.cahandler._config_url_load(parser)\n        self.assertEqual(\"foo\", self.cahandler.url)\n\n    @patch.dict(\"os.environ\", {\"url_variable\": \"foo1\"})\n    def test_058__config_url_load(self):\n        \"\"\"test _config_url_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"url_variable\": \"url_variable\"}\n        self.cahandler._config_url_load(parser)\n        self.assertEqual(\"foo1\", self.cahandler.url)\n\n    @patch.dict(\"os.environ\", {\"url_variable\": \"foo1\"})\n    def test_059__config_url_load(self):\n        \"\"\"test _config_url_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"url_variable\": \"url_variable\", \"url\": \"foo\"}\n        self.cahandler._config_url_load(parser)\n        self.assertEqual(\"foo\", self.cahandler.url)\n\n    @patch.dict(\"os.environ\", {\"url_variable\": \"foo1\"})\n    def test_060__config_url_load(self):\n        \"\"\"test _config_url_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"url_variable\": \"doesnotexist\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_url_load(parser)\n        self.assertFalse(self.cahandler.url)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load url_variable from environment: 'doesnotexist'\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mscertsrv_ca_handler.handler_config_check\")\n    def test_061_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_mswcce_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, R0913, W0212\nimport sys\nimport unittest\nfrom unittest.mock import patch, mock_open, Mock\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for mswcce_ca_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.mswcce_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        pass\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_002_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load wrongly configured cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch.dict(\"os.environ\", {\"host_var\": \"host_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load host from variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"host_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"host_var\", self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"host_var\": \"host_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load host from not_existing variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"unk\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load host variable from environment: 'unk'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch.dict(\"os.environ\", {\"host_var\": \"host_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - overwrite host variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host_variable\": \"host_var\", \"host\": \"host_local\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"host_local\", self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\"INFO:test_a2c:Overwrite host\", lcm.output)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_007_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load host from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host\": \"host_local\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"host_local\", self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_008_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load user from variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"user_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(\"user_var\", self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_009_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load user from not existing variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"unk\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load user variable from environment: 'unk'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_010_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - overwrite user variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user_variable\": \"user_var\", \"user\": \"user_local\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(\"user_local\", self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\"INFO:test_a2c:Overwrite user\", lcm.output)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_011_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load user from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"user\": \"user_local\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(\"user_local\", self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_012_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load password from variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password_variable\": \"password_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertEqual(\"password_var\", self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_013_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load password from not existing variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password_variable\": \"unk\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load password variable from environment: 'unk'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch.dict(\"os.environ\", {\"password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - overwrite password variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"password_variable\": \"password_var\",\n            \"password\": \"password_local\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertEqual(\"password_local\", self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertIn(\"INFO:test_a2c:Overwrite password\", lcm.output)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load password from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"password\": \"password_local\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertEqual(\"password_local\", self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load target domain from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"target_domain\": \"target_domain\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"target_domain\", self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load domain_controller from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"domain_controller\": \"domain_controller\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"domain_controller\", self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load domain_controller from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"dns_server\": \"dns_server\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"dns_server\", self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load ca_name from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_name\": \"ca_name\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load ca_name from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"template\": \"template\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\"template\", self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.proxy_check\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load_cfg, mock_json, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        mock_load_cfg.return_value = {\"DEFAULT\": {\"proxy_server_list\": \"foo\"}}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_chk.called)\n        self.assertEqual(\n            {\"http\": \"proxy.bar.local\", \"https\": \"proxy.bar.local\"},\n            self.cahandler.proxy,\n        )\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.proxy_check\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load_cfg, mock_json, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies failed with exception in json.load\"\"\"\n        mock_load_cfg.return_value = {\"DEFAULT\": {\"proxy_server_list\": \"foo\"}}\n        mock_json.side_effect = Exception(\"exc_load_config\")\n        mock_chk.side = \"proxy.bar.local\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertFalse(mock_chk.called)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load proxy_server_list from configuration: exc_load_config\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"use_kerberos\": \"True\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_025_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"use_kerberos\": True}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_026_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"use_kerberos\": False}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_027_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"use_kerberos\": \"False\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_028_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"use_kerberos\": \"aaaa\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse 'use_kerberos' from configuration. Using default value False. Error: Not a boolean: aaaa\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_029_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"allowed_domainlist\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_030_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": \"wrongstring\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_031_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"timeout\": 20}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(20, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_032_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"timeout\": \"20\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertEqual(20, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_033_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"timeout\": \"aaaa\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse 'timeout' from configuration. Using default value 5. Error: invalid literal for int() with base 10: 'aaaa'\",\n            lcm.output,\n        )\n        self.assertEqual(5, self.cahandler.timeout)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_034_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_config_log\": True}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertTrue(self.cahandler.enrollment_config_log)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_035_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_config_log\": False}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertFalse(self.cahandler.enrollment_config_log)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_036_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_config_log\": \"False\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertFalse(self.cahandler.enrollment_config_log)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.load_config\")\n    def test_037_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template from config file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"enrollment_config_log\": \"aaaa\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.host)\n        self.assertFalse(self.cahandler.user)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.template)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertFalse(self.cahandler.target_domain)\n        self.assertFalse(self.cahandler.domain_controller)\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.use_kerberos)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load enrollment_config_log from configuration: Not a boolean: aaaa\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.enrollment_config_log)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_038__file_load(self):\n        \"\"\"test _load file()\"\"\"\n        self.assertEqual(\"foo\", self.cahandler._file_load(\"filename\"))\n\n    @patch(\"builtins.open\")\n    def test_039__file_load(self, mock_op):\n        \"\"\"test _load file()\"\"\"\n        mock_op.side_effect = Exception(\"ex_mock_open\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._file_load(\"filename\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load file 'filename'. Error: ex_mock_open\",\n            lcm.output,\n        )\n\n    def test_040_revoke(self):\n        \"\"\"test revocation\"\"\"\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Revocation is not supported.\",\n            ),\n            self.cahandler.revoke(\"cert\", \"rev_reason\", \"rev_date\"),\n        )\n\n    def test_041_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_042_trigger(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    def test_043_enroll(self, mock_rcr):\n        \"\"\"test enrollment - unconfigured\"\"\"\n        self.assertEqual(\n            (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    def test_044_enroll(self, mock_rcr):\n        \"\"\"test enrollment - host unconfigured\"\"\"\n        self.cahandler.host = None\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.assertEqual(\n            (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    def test_045_enroll(self, mock_rcr):\n        \"\"\"test enrollment - user unconfigured\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = None\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.assertEqual(\n            (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    def test_046_enroll(self, mock_rcr):\n        \"\"\"test enrollment - password unconfigured\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = None\n        self.cahandler.template = \"template\"\n        self.assertEqual(\n            (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    def test_047_enroll(self, mock_rcr):\n        \"\"\"test enrollment - template unconfigured\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = None\n        self.assertEqual(\n            (\n                \"Configuration incomplete: host, user, password, or template is missing.\",\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertFalse(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_048_enroll(self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr):\n        \"\"\"test enrollment - ca_server.get_cert() triggers exception\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = \"file_load\"\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.side_effect = Exception(\"ex_b2s\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\n                    \"Certificate bundling failed: CA certificate or issued certificate is missing.\",\n                    None,\n                    None,\n                    None,\n                ),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\"ERROR:test_a2c:Enrollment failed with error: ex_b2s\", lcm.output)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundling failed: CA certificate or issued certificate is missing.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_049_enroll(self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr):\n        \"\"\"test enrollment - no certificate returned by ca_server.get_cert()\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = \"file_load\"\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\n                    \"Certificate bundling failed: CA certificate or issued certificate is missing.\",\n                    None,\n                    None,\n                    None,\n                ),\n                self.cahandler.enroll(\"csr\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundling failed: CA certificate or issued certificate is missing.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_050_enroll(self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr):\n        \"\"\"test enrollment - certificate and bundling successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = \"file_load\"\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = \"b2s\"\n        self.assertEqual(\n            (None, \"b2sfile_load\", \"b2s\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_051_enroll(self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr):\n        \"\"\"test enrollment - certificate and bundling successful\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = \"file_load\"\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = \"b2s\"\n        self.assertEqual(\n            (None, \"b2sfile_load\", \"b2s\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_052_enroll(self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr):\n        \"\"\"test enrollment - certificate and bundling successful replacement test\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = \"file_load\"\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = (\n            \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\"\n        )\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\nfile_load\",\n                \"b2s_replacement\",\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_rcr.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_053_enroll(\n        self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr, mock_eab\n    ):\n        \"\"\"test enrollment - certificate and bundling successful replacement test\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = None\n        mock_s2b.return_value = \"s2b\"\n        mock_eab.return_value = None\n        mock_b2s.return_value = (\n            \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\"\n        )\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\",\n                \"b2s_replacement\",\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_rcr.called)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_054_enroll(\n        self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr, mock_eab\n    ):\n        \"\"\"test enrollment - certificate and bundling successful replacement test\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.header_info_field = \"header_info\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = None\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = (\n            \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\"\n        )\n        mock_eab.return_value = \"error\"\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertFalse(mock_rcr.called)\n        self.assertEqual(\"template\", self.cahandler.template)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler.request_create\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._file_load\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.build_pem_file\")\n    def test_055_enroll(\n        self, mock_pem, mock_file, mock_b2s, mock_s2b, mock_rcr, mock_eab\n    ):\n        \"\"\"test enrollment - certificate and bundling successful replacement test\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.user = \"user\"\n        self.cahandler.password = \"password\"\n        self.cahandler.template = \"template\"\n        self.cahandler.header_info_field = \"header_info\"\n        mock_rcr.return_value = Mock(return_value=\"raw_data\")\n        mock_file.return_value = None\n        mock_s2b.return_value = \"s2b\"\n        mock_b2s.return_value = (\n            \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\"\n        )\n        mock_eab.return_value = None\n        self.assertEqual(\n            (\n                None,\n                \"-----BEGIN CERTIFICATE-----\\nb2s_replacement\\n-----END CERTIFICATE-----\\n\",\n                \"b2s_replacement\",\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_rcr.called)\n        self.assertEqual(\"template\", self.cahandler.template)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.Request\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.Target\")\n    def test_056_request_create(self, mock_target, mock_request, mock_ecl):\n        \"\"\"test request create\"\"\"\n        mock_target.return_value = True\n        mock_request.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.cahandler.request_create())\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.Request\")\n    @patch(\"examples.ca_handler.mswcce_ca_handler.Target\")\n    def test_057_request_create(self, mock_target, mock_request, mock_ecl):\n        \"\"\"test request create\"\"\"\n        mock_target.return_value = True\n        mock_request.return_value = \"foo\"\n        self.cahandler.enrollment_config_log = True\n        self.assertEqual(\"foo\", self.cahandler.request_create())\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._config_load\")\n    def test_058__enter(self, mock_cfgload):\n        \"\"\"CAhandler._enter() with config load\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfgload.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.CAhandler._config_load\")\n    def test_059__enter(self, mock_cfgload):\n        \"\"\"CAhandler._enter() with config load\"\"\"\n        self.cahandler.host = None\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfgload.called)\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.header_info_get\")\n    def test_060_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [\n            {\n                \"header_info\": '{\"header_field\": \"template=foo lego-cli/4.14.2 xenolf-acme/4.14.2 (release; linux; amd64)\"}'\n            }\n        ]\n        self.cahandler.header_info_field = \"header_field\"\n        self.assertEqual(\"foo\", self.cahandler._template_name_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.header_info_get\")\n    def test_061_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [\n            {\n                \"header_info\": '{\"header_field\": \"Template=foo lego-cli/4.14.2 xenolf-acme/4.14.2 (release; linux; amd64)\"}'\n            }\n        ]\n        self.cahandler.header_info_field = \"header_field\"\n        self.assertEqual(\"foo\", self.cahandler._template_name_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.header_info_get\")\n    def test_062_template_name_get(self, mock_header):\n        \"\"\"test _template_name_get()\"\"\"\n        mock_header.return_value = [{\"header_info\": \"header_info\"}]\n        self.cahandler.header_info_field = \"header_field\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._template_name_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse template from header info: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    def test_063_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\", \"bar\", \"foobar\"]'}}\n        self.cahandler._config_headerinfo_load(config_dic)\n        self.assertEqual(\"foo\", self.cahandler.header_info_field)\n\n    def test_064_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": '[\"foo\"]'}}\n        self.cahandler._config_headerinfo_load(config_dic)\n        self.assertEqual(\"foo\", self.cahandler.header_info_field)\n\n    def test_065_config_headerinfo_load(self):\n        \"\"\"test config_headerinfo_load()\"\"\"\n        config_dic = {\"Order\": {\"header_info_list\": \"foo\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_headerinfo_load(config_dic)\n        self.assertFalse(self.cahandler.header_info_field)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse header_info_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.mswcce_ca_handler.handler_config_check\")\n    def test_066_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_nclm_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, Mock\nimport requests\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.ca_handler.nclm_ca_handler import CAhandler\n\n        self.cahandler = CAhandler(False, self.logger)\n        # self.cahandler.api_host = 'api_host'\n        # self.cahandler.auth = 'auth'\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch.object(requests, \"post\")\n    def test_002__api_post(self, mock_req):\n        \"\"\"test _api_post successful run\"\"\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._api_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_003__api_post(self, mock_post):\n        \"\"\"CAhandler.get_ca() returns an http error\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_post.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"exc_api_post\", self.cahandler._api_post(\"url\", \"data\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API POST request failed: exc_api_post\",\n            lcm.output,\n        )\n\n    @patch.object(requests, \"post\")\n    def test_004__api_post(self, mock_req):\n        \"\"\"test _api_post successful run\"\"\"\n        mockresponse = Mock()\n        mock_req.return_value = mockresponse\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = Exception(\"json_exc\")\n        self.assertEqual(\n            {\"status\": \"status_code\"}, self.cahandler._api_post(\"url\", \"data\")\n        )\n\n    def test_005__config_check(self):\n        \"\"\"CAhandler._config.check() no api_host\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"api_host to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:Missing \"api_host\" in configuration.', lcm.output)\n\n    def test_006__config_check(self):\n        \"\"\"CAhandler._config.check() no api_user\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"api_user to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:Missing \"api_user\" in configuration.', lcm.output)\n\n    def test_007__config_check(self):\n        \"\"\"CAhandler._config.check() no api_user\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\"api_user\": False}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"api_user to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:Missing \"api_user\" in configuration.', lcm.output)\n\n    def test_008__config_check(self):\n        \"\"\"CAhandler._config.check() no api_password\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\"api_user\": \"api_user\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"api_password to be set in config file\", self.cahandler.error)\n        self.assertIn(\n            'ERROR:test_a2c:Missing \"api_password\" in configuration.', lcm.output\n        )\n\n    def test_009__config_check(self):\n        \"\"\"CAhandler._config.check() no api_password\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\"api_user\": \"api_user\", \"api_password\": False}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"api_password to be set in config file\", self.cahandler.error)\n        self.assertIn(\n            'ERROR:test_a2c:Missing \"api_password\" in configuration.', lcm.output\n        )\n\n    def test_010__config_check(self):\n        \"\"\"CAhandler._config.check() no tsg_name\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"tsg_name to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:\"tsg_name\" to be set in config file', lcm.output)\n\n    def test_011__config_check(self):\n        \"\"\"CAhandler._config.check() no tsg_name\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n        }\n        self.cahandler.container_info_dic = {\"name\": False}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"tsg_name to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:\"tsg_name\" to be set in config file', lcm.output)\n\n    def test_012__config_check(self):\n        \"\"\"CAhandler._config.check() no ca_name\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n        }\n        self.cahandler.container_info_dic = {\"name\": \"name\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertEqual(\"ca_name to be set in config file\", self.cahandler.error)\n        self.assertIn('ERROR:test_a2c:\"ca_name\" to be set in config file', lcm.output)\n\n    def test_013__config_check(self):\n        \"\"\"CAhandler._config.check() ca_bundle False\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.credential_dic = {\n            \"api_user\": \"api_user\",\n            \"api_password\": \"api_password\",\n        }\n        self.cahandler.container_info_dic = {\"name\": \"name\"}\n        self.cahandler.ca_name = \"ca_name\"\n        self.cahandler.ca_id_list = [\"id1\", \"id2\"]\n        self.cahandler.ca_bundle = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_check()\n        self.assertFalse(self.cahandler.error)\n        self.assertIn(\n            'WARNING:test_a2c:CA bundle validation is disabled (\"ca_bundle\" set to False). Server certificate will not be validated.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load no cahandler section\"\"\"\n        parser = configparser.ConfigParser()\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load api_host\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_host\": \"api_host\", \"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"api_host\", self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load api_user\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_user\": \"api_user\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": \"api_user\", \"api_password\": None},\n            self.cahandler.credential_dic,\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load api_password\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_password\": \"api_password\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": \"api_password\"},\n            self.cahandler.credential_dic,\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load ca_name\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_name\": \"ca_name\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertEqual(\"ca_name\", self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load tsg_name\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"tsg_name\": \"tsg_name\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"WARNING:test_a2c:Configuration uses deprecated 'tsg_name'. Use 'container_name' instead.\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_020_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load ca_bundle string\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_021_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load ca_bundle False\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": False}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertEqual({\"name\": None, \"id\": None}, self.cahandler.template_info_dic)\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_022_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load template_name\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"template_name\": \"template_name\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertFalse(self.cahandler.ca_name)\n        self.assertEqual(\n            {\"name\": \"template_name\", \"id\": None}, self.cahandler.template_info_dic\n        )\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_023_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load username from variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_user_variable\": \"api_user_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": \"user_var\", \"api_password\": None},\n            self.cahandler.credential_dic,\n        )\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load username from non existing\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_user_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load API user from environment: 'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"api_user_var\": \"user_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_025_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load username from wich gets overwritten from cfg-file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_user_variable\": \"api_user_var\",\n            \"api_user\": \"api_user\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": \"api_user\", \"api_password\": None},\n            self.cahandler.credential_dic,\n        )\n        self.assertIn(\"INFO:test_a2c:Overwrite api_user\", lcm.output)\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_026_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load password from variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_password_variable\": \"api_password_var\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": \"password_var\"},\n            self.cahandler.credential_dic,\n        )\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_027_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load password from non existing variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"api_password_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_user\": None, \"api_password\": None}, self.cahandler.credential_dic\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load password_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"api_password_var\": \"password_var\"})\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_028_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load load password from variable which gets overwritten\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"api_password_variable\": \"api_password_var\",\n            \"api_password\": \"api_password\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.api_host)\n        self.assertEqual(\n            {\"api_password\": \"api_password\", \"api_user\": None},\n            self.cahandler.credential_dic,\n        )\n        self.assertIn(\"INFO:test_a2c:Overwrite api_password\", lcm.output)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_029_config_load(self, mock_load_cfg, mock_json, mock_url):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"foo\": \"bar\"}\n        mock_json.return_value = \"foo\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_030_config_load(self, mock_load_cfg, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"host\": \"bar:8888\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_chk.called)\n        self.assertEqual(\n            {\"http\": \"proxy.bar.local\", \"https\": \"proxy.bar.local\"},\n            self.cahandler.proxy,\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.proxy_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.parse_url\")\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_031_config_load(self, mock_load_cfg, mock_json, mock_url, mock_chk):\n        \"\"\"test _config_load ca_handler configured load proxies\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"DEFAULT\"] = {\"proxy_server_list\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_url.return_value = {\"host\": \"bar\"}\n        mock_json.return_value = \"foo.bar.local\"\n        mock_chk.return_value = \"proxy.bar.local\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_json.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_chk.called)\n        self.assertFalse(self.cahandler.proxy)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to load proxy_server_list from configuration: not enough values to unpack (expected 2, got 1)\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_032_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load request_delta_treshold\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 10}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(10, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_033_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load request_delta_treshold\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aa\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(20, self.cahandler.request_timeout)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.load_config\")\n    def test_034_config_load(self, mock_load_cfg):\n        \"\"\"CAhandler._config_load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"container_name\": \"container_name\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\n            {\"name\": \"container_name\", \"id\": None}, self.cahandler.container_info_dic\n        )\n\n    @patch(\"requests.post\")\n    @patch(\"requests.get\")\n    def test_035__login(self, mock_get, mock_post):\n        \"\"\"CAhandler._unusedrequests_get\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse1 = Mock()\n        mockresponse1.status_code = \"500\"\n        mockresponse1.ok = None\n        mock_get.return_value = mockresponse1\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._login()\n        self.assertIn(\"ERROR:test_a2c:Login failed. Error: 500\", lcm.output)\n        self.assertFalse(mock_post.called)\n        self.assertFalse(self.cahandler.headers)\n\n    @patch(\"requests.post\")\n    @patch(\"requests.get\")\n    def test_036__login(self, mock_get, mock_post):\n        \"\"\"CAhandler._unusedrequests_get\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse1 = Mock()\n        mockresponse1.status_code = \"200\"\n        mockresponse1.json = lambda: {\"versionNumber\": \"versionNumber\"}\n        mockresponse1.ok = True\n        mock_get.return_value = mockresponse1\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"500\"\n        mockresponse2.json = lambda: {\n            \"foo\": \"bar\",\n            \"username\": \"username\",\n            \"realms\": \"realms\",\n        }\n        mockresponse2.ok = None\n        mock_post.return_value = mockresponse2\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._login()\n        self.assertIn(\"ERROR:test_a2c:Login Error: 500\", lcm.output)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.headers)\n        self.assertEqual(\"versionNumber\", self.cahandler.nclm_version)\n\n    @patch(\"requests.post\")\n    @patch(\"requests.get\")\n    def test_037__login(self, mock_get, mock_post):\n        \"\"\"CAhandler._unusedrequests_get\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse1 = Mock()\n        mockresponse1.status_code = \"200\"\n        mockresponse1.json = lambda: {\"versionNumber\": \"versionNumber\"}\n        mockresponse1.ok = True\n        mock_get.return_value = mockresponse1\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"200\"\n        mockresponse2.json = lambda: {\n            \"foo\": \"bar\",\n            \"username\": \"username\",\n            \"realms\": \"realms\",\n        }\n        mockresponse2.ok = True\n        mock_post.return_value = mockresponse2\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._login()\n        self.assertIn(\n            \"ERROR:test_a2c:No token returned after logging in. Aborting.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_post.called)\n        self.assertFalse(self.cahandler.headers)\n        self.assertEqual(\"versionNumber\", self.cahandler.nclm_version)\n\n    @patch(\"requests.post\")\n    @patch(\"requests.get\")\n    def test_038__login(self, mock_get, mock_post):\n        \"\"\"CAhandler._unusedrequests_get\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse1 = Mock()\n        mockresponse1.status_code = \"200\"\n        mockresponse1.json = lambda: {\"versionNumber\": \"versionNumber\"}\n        mockresponse1.ok = True\n        mock_get.return_value = mockresponse1\n        mockresponse2 = Mock()\n        mockresponse2.status_code = \"200\"\n        mockresponse2.json = lambda: {\n            \"access_token\": \"access_token\",\n            \"username\": \"username\",\n            \"realms\": \"realms\",\n        }\n        mockresponse2.ok = True\n        mock_post.return_value = mockresponse2\n        self.cahandler._login()\n        self.assertTrue(mock_post.called)\n        self.assertEqual(\n            {\"Authorization\": \"Bearer access_token\"}, self.cahandler.headers\n        )\n        self.assertEqual(\"versionNumber\", self.cahandler.nclm_version)\n\n    def test_039_poll(self):\n        \"\"\"CAhandler.poll()\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_040_trigger(self):\n        \"\"\"CAhandler.trigger()\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch(\"requests.get\")\n    def test_041_container_id_lookup(self, mock_get):\n        \"\"\"CAhandler._container_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"items\": [{\"name\": \"name1\", \"id\": \"id1\"}, {\"name\": \"name2\", \"id\": \"id2\"}]\n        }\n        mock_get.return_value = mockresponse\n        self.cahandler.container_info_dic = {\"name\": \"name1\", \"id\": None}\n        self.cahandler._container_id_lookup()\n        self.assertEqual(\n            {\"name\": \"name1\", \"id\": \"id1\"}, self.cahandler.container_info_dic\n        )\n\n    @patch(\"requests.get\")\n    def test_042_container_id_lookup(self, mock_get):\n        \"\"\"CAhandler._container_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"items\": [{\"name1\": \"name1\", \"id\": \"id1\"}, {\"name\": \"name2\", \"id\": \"id2\"}]\n        }\n        mock_get.return_value = mockresponse\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": None}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._container_id_lookup()\n        self.assertIn(\n            \"ERROR:test_a2c:Incomplete container response: {'name1': 'name1', 'id': 'id1'}\",\n            lcm.output,\n        )\n        self.assertEqual(\n            {\"name\": \"name\", \"id\": None}, self.cahandler.container_info_dic\n        )\n\n    @patch(\"requests.get\")\n    def test_043_container_id_lookup(self, mock_get):\n        \"\"\"CAhandler._container_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"foo\": [{\"name\": \"name1\", \"id\": \"id1\"}, {\"name\": \"name2\", \"id\": \"id2\"}]\n        }\n        mock_get.return_value = mockresponse\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": None}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._container_id_lookup()\n        self.assertIn(\n            \"ERROR:test_a2c:No target system groups found for filter: name.\",\n            lcm.output,\n        )\n        self.assertEqual(\n            {\"name\": \"name\", \"id\": None}, self.cahandler.container_info_dic\n        )\n\n    @patch(\"requests.get\")\n    def test_044_container_id_lookup(self, mock_req):\n        \"\"\"CAhandler._container_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mock_req.side_effect = Exception(\"exc_container_id_lookup\")\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": None}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._container_id_lookup()\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to retrieve container id: exc_container_id_lookup\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:No target system groups found for filter: name.\",\n            lcm.output,\n        )\n        self.assertEqual(\n            {\"name\": \"name\", \"id\": None}, self.cahandler.container_info_dic\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._templates_enumerate\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_list_get\")\n    def test_045__template_id_lookup(self, mock_list, mock_enum):\n        \"\"\"CAhandler._template_id_lookup\"\"\"\n        mock_list.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._template_id_lookup(\"caid\")\n        self.assertIn(\n            \"ERROR:test_a2c:No templates found for filter: None.\",\n            lcm.output,\n        )\n        self.assertFalse(mock_enum.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._templates_enumerate\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_list_get\")\n    def test_046__template_id_lookup(self, mock_list, mock_enum):\n        \"\"\"CAhandler._template_id_lookup\"\"\"\n        mock_list.return_value = {\"items\": [\"foo\", \"bar\"]}\n        self.cahandler._template_id_lookup(\"caid\")\n        self.assertTrue(mock_enum.called)\n\n    @patch(\"requests.get\")\n    def test_047__template_list_get(self, mock_get):\n        \"\"\"CAhandler._template_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_get.return_value = mockresponse\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._template_list_get(6))\n\n    @patch(\"requests.get\")\n    def test_048__template_list_get(self, mock_get):\n        \"\"\"CAhandler._template_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mock_get.side_effect = Exception(\"req_exc\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._template_list_get(6))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to retrieve template list: req_exc\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    def test_049__template_list_get(self, mock_get):\n        \"\"\"CAhandler._template_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"items\": \"bar\"}\n        mock_get.return_value = mockresponse\n        self.assertEqual({\"items\": \"bar\"}, self.cahandler._template_list_get(6))\n\n    def test_050__templates_enumerate(self):\n        \"\"\"CAhandler._templates_enumerate()\"\"\"\n        template_list = {\n            \"items\": [{\"name\": \"foo\", \"id\": \"id\"}, {\"name\": \"foo1\", \"id\": \"id1\"}]\n        }\n        self.cahandler.template_info_dic = {\"name\": \"foo\"}\n        self.cahandler._templates_enumerate(template_list)\n        self.assertEqual({\"id\": \"id\", \"name\": \"foo\"}, self.cahandler.template_info_dic)\n\n    def test_051__templates_enumerate(self):\n        \"\"\"CAhandler._templates_enumerate()\"\"\"\n        template_list = {\n            \"items\": [{\"name\": \"foo\", \"id\": \"id\"}, {\"name\": \"foo1\", \"id\": \"id1\"}]\n        }\n        self.cahandler.template_info_dic = {\"name\": \"foo1\"}\n        self.cahandler._templates_enumerate(template_list)\n        self.assertEqual(\n            {\"id\": \"id1\", \"name\": \"foo1\"}, self.cahandler.template_info_dic\n        )\n\n    def test_052__templates_enumerate(self):\n        \"\"\"CAhandler._templates_enumerate()\"\"\"\n        template_list = {\n            \"items\": [\n                {\"name\": \"foo\", \"id\": \"id\"},\n                {\"name\": \"foo1\", \"id\": \"id1\"},\n                {\"name\": \"foo\", \"id\": \"id2\"},\n            ]\n        }\n        self.cahandler.template_info_dic = {\"name\": \"foo\"}\n        self.cahandler._templates_enumerate(template_list)\n        self.assertEqual({\"id\": \"id\", \"name\": \"foo\"}, self.cahandler.template_info_dic)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_053__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter\"\"\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_check.called)\n        self.assertTrue(mock_login.called)\n        self.assertTrue(mock_lookup.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_054__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter  with host already defined\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_load.called)\n        self.assertFalse(mock_check.called)\n        self.assertTrue(mock_login.called)\n        self.assertTrue(mock_lookup.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_055__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter with header defined\"\"\"\n        self.cahandler.headers = \"header\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_check.called)\n        self.assertFalse(mock_login.called)\n        self.assertTrue(mock_lookup.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_056__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter with error defined\"\"\"\n        self.cahandler.error = \"error\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_check.called)\n        self.assertFalse(mock_login.called)\n        self.assertFalse(mock_lookup.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_057__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter with tst_info_dic defined\"\"\"\n        self.cahandler.container_info_dic = {\"id\": \"foo\"}\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_check.called)\n        self.assertTrue(mock_login.called)\n        self.assertFalse(mock_lookup.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_load\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._login\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._container_id_lookup\")\n    def test_058__enter__(self, mock_lookup, mock_login, mock_check, mock_load):\n        \"\"\"test enter with error defined\"\"\"\n        self.cahandler.container_info_dic = {\"id\": \"foo\"}\n        self.cahandler.error = \"error\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_check.called)\n        self.assertFalse(mock_login.called)\n        self.assertFalse(mock_lookup.called)\n\n    def test_059__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        ca_list = {}\n        self.assertFalse(self.cahandler._ca_id_get(ca_list))\n\n    def test_060__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        ca_list = {\"ca\": {\"foo\": \"bar\"}}\n        self.assertFalse(self.cahandler._ca_id_get(ca_list))\n\n    def test_061__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        ca_list = {\"items\": \"bar\"}\n        self.assertFalse(self.cahandler._ca_id_get(ca_list))\n\n    def test_062__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        ca_list = {\"items\": [{\"foo\": \"bar\"}]}\n        self.assertFalse(self.cahandler._ca_id_get(ca_list))\n\n    def test_063__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        ca_list = {\"items\": [{\"name\": \"ca_name\", \"id\": \"id\"}]}\n        self.assertEqual(\"id\", self.cahandler._ca_id_get(ca_list))\n\n    def test_064__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        self.cahandler.ca_name = \"ca_name\"\n        ca_list = {\"items\": [{\"name\": \"ca_name\", \"id1\": \"id\"}]}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._ca_id_get(ca_list))\n        self.assertIn(\n            \"ERROR:test_a2c:CA response missing policyLinkId field.\",\n            lcm.output,\n        )\n\n    def test_065__ca_id_get(self):\n        \"\"\"test _ca_id_get()\"\"\"\n        self.cahandler.ca_name = \"ca_name1\"\n        ca_list = {\"items\": [{\"name\": \"ca_name\", \"id\": \"id\"}]}\n        self.assertFalse(self.cahandler._ca_id_get(ca_list))\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_id_get\")\n    @patch(\"requests.get\")\n    def test_066__ca_policylink_id_lookup(self, mock_req, mock_caid):\n        \"\"\"test _ca_policylink_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.container_info_dic = {\"id\": \"id\"}\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"items\": [\"foo\", \"bar\", \"foo\", \"bar\"]}\n        mock_req.return_value = mockresponse\n        mock_caid.return_value = 10\n        self.assertEqual(10, self.cahandler._ca_policylink_id_lookup())\n        self.assertTrue(mock_caid.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_id_get\")\n    @patch(\"requests.get\")\n    def test_067__ca_policylink_id_lookup(self, mock_req, mock_caid):\n        \"\"\"test _ca_policylink_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.container_info_dic = {\"id\": \"id\"}\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"items\": [\"foo\", \"bar\", \"foo\", \"bar\"]}\n        mock_req.return_value = mockresponse\n        mock_caid.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._ca_policylink_id_lookup())\n        self.assertIn(\n            \"ERROR:test_a2c:No policy link ID found for CA name: None\",\n            lcm.output,\n        )\n        self.assertTrue(mock_caid.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_id_get\")\n    @patch(\"requests.get\")\n    def test_068__ca_policylink_id_lookup(self, mock_req, mock_caid):\n        \"\"\"test _ca_policylink_id_lookup()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.container_info_dic = {\"id\": \"id\"}\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"foo\": [\"foo\", \"bar\", \"foo\", \"bar\"]}\n        mock_req.return_value = mockresponse\n        mock_caid.return_value = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._ca_policylink_id_lookup())\n        self.assertIn(\n            \"ERROR:test_a2c:No policy link ID found for CA name: None\",\n            lcm.output,\n        )\n        self.assertIn(\"ERROR:test_a2c:No CAs found in issuer response.\", lcm.output)\n        self.assertFalse(mock_caid.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_bundle_build\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._csr_post\")\n    def test_069__cert_enroll(self, mock_post, mock_idget, mock_build):\n        \"\"\"test _cert_enroll()\"\"\"\n        mock_post.return_value = \"mock_post\"\n        mock_idget.return_value = \"mock_idget\"\n        mock_build.return_value = (\"error\", \"bundle\", \"raw\")\n        self.assertEqual(\n            (\"error\", \"bundle\", \"raw\", \"mock_idget\"),\n            self.cahandler._cert_enroll(\"cr\", \"policylink_id\"),\n        )\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_idget.called)\n        self.assertTrue(mock_build.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_bundle_build\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._csr_post\")\n    def test_070__cert_enroll(self, mock_post, mock_idget, mock_build):\n        \"\"\"test _cert_enroll()\"\"\"\n        mock_post.return_value = \"mock_post\"\n        mock_idget.return_value = None\n        mock_build.return_value = (\"error\", \"bundle\", \"raw\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Certifcate_id lookup failed\", None, None, None),\n                self.cahandler._cert_enroll(\"cr\", \"policylink_id\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate ID lookup failed for job: mock_post\",\n            lcm.output,\n        )\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_idget.called)\n        self.assertFalse(mock_build.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_bundle_build\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._csr_post\")\n    def test_071__cert_enroll(self, mock_post, mock_idget, mock_build):\n        \"\"\"test _cert_enroll()\"\"\"\n        mock_post.return_value = None\n        mock_idget.return_value = \"mock_idget\"\n        mock_build.return_value = (\"error\", \"bundle\", \"raw\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"job_id lookup failed\", None, None, None),\n                self.cahandler._cert_enroll(\"cr\", \"policylink_id\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Job ID lookup failed during certificate enrollment.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_idget.called)\n        self.assertFalse(mock_build.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.build_pem_file\")\n    def test_072__csr_post(self, mock_pem, mock_enc, mock_convert, mock_post):\n        \"\"\"test _csr_post()\"\"\"\n        mock_pem.return_value = \"mock_pem\"\n        mock_enc.return_value = \"mock_enc\"\n        mock_convert.return_value = \"mock_convert\"\n        mock_post.return_value = {\"id\": \"id\", \"foo\": \"bar\"}\n        self.assertEqual(\"id\", self.cahandler._csr_post(\"csr\", \"policylink_id\"))\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_enc.called)\n        self.assertTrue(mock_pem.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.build_pem_file\")\n    def test_073__csr_post(self, mock_pem, mock_enc, mock_convert, mock_post):\n        \"\"\"test _csr_post()\"\"\"\n        mock_pem.return_value = \"mock_pem\"\n        mock_enc.return_value = \"mock_enc\"\n        mock_convert.return_value = \"mock_convert\"\n        mock_post.return_value = {\"foo\": \"bar\"}\n        self.cahandler.template_info_dic = {\"id\": \"id\"}\n        self.assertFalse(self.cahandler._csr_post(\"csr\", \"policylink_id\"))\n        self.assertTrue(mock_convert.called)\n        self.assertTrue(mock_enc.called)\n        self.assertTrue(mock_pem.called)\n\n    @patch(\"requests.get\")\n    def test_074__issuer_certid_get(self, mock_req):\n        \"\"\"test _issuer_certid_get()\"\"\"\n        cert_dic = {\"urls\": {\"issuer\": \"issuer\"}}\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"urls\": {\"certificate\": \"foo/v2/certificates/\"}}\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"foo\", True), self.cahandler._issuer_certid_get(cert_dic))\n\n    @patch(\"requests.get\")\n    def test_075__issuer_certid_get(self, mock_req):\n        \"\"\"test _issuer_certid_get()\"\"\"\n        cert_dic = {\"urls\": {\"issuer\": \"issuer\"}}\n        self.cahandler.api_host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"urls\": {\"bar\": \"foo/v2/certificates/\"}}\n        mock_req.return_value = mockresponse\n        self.assertEqual((None, False), self.cahandler._issuer_certid_get(cert_dic))\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._issuer_certid_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.build_pem_file\")\n    @patch(\"requests.get\")\n    def test_076__cert_bundle_build(self, mock_req, mock_pem, mock_certid):\n        \"\"\"test _cert_bundle_build()\"\"\"\n        mock_pem.return_value = \"mock_pem\"\n        mock_certid.return_value = (\"id\", False)\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"der\": \"der\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (None, \"mock_pem\", \"der\"), self.cahandler._cert_bundle_build(\"cert_id\")\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._issuer_certid_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.build_pem_file\")\n    @patch(\"requests.get\")\n    def test_077__cert_bundle_build(self, mock_req, mock_pem, mock_certid):\n        \"\"\"test _cert_bundle_build()\"\"\"\n        mock_pem.side_effect = [\"mock_pem1\", \"mock_pem2\"]\n        mock_certid.side_effect = [(\"id1\", True), (\"id2\", False)]\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"der\": \"der\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (None, \"mock_pem2\", \"der\"), self.cahandler._cert_bundle_build(\"cert_id\")\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._issuer_certid_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.build_pem_file\")\n    @patch(\"requests.get\")\n    def test_078__cert_bundle_build(self, mock_req, mock_pem, mock_certid):\n        \"\"\"test _cert_bundle_build()\"\"\"\n        mock_pem.return_value = \"\"\n        mock_certid.return_value = (\"id\", False)\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"der\": \"der\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (None, None, \"der\"), self.cahandler._cert_bundle_build(\"cert_id\")\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_079__cert_id_get(self, mock_req, mock_sleep):\n        \"\"\"test _cert_id_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"status\": \"done\",\n            \"entities\": [{\"ref\": \"certificate\", \"url\": \"foo/v2/certificates/\"}],\n        }\n        mock_req.return_value = mockresponse\n        self.assertEqual(\"foo\", self.cahandler._cert_id_get(10))\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_080__cert_id_get(self, mock_req, mock_sleep):\n        \"\"\"test _cert_id_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\n            \"status\": \"done\",\n            \"entities\": [{\"foo\": \"certificate\", \"url\": \"foo/v2/certificates/\"}],\n        }\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._cert_id_get(10))\n        self.assertIn(\n            \"ERROR:test_a2c:Job completed but certificate reference is missing or malformed: {'status': 'done', 'entities': [{'foo': 'certificate', 'url': 'foo/v2/certificates/'}]}\",\n            lcm.output,\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_081__cert_id_get(self, mock_req, mock_sleep):\n        \"\"\"test _cert_id_get()\"\"\"\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\n            \"status\": \"note\",\n            \"entities\": [{\"ref\": \"certificate\", \"url\": \"foo1/v2/certificates/\"}],\n        }\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\n            \"status\": \"done\",\n            \"entities\": [{\"ref\": \"certificate\", \"url\": \"foo2/v2/certificates/\"}],\n        }\n        mock_req.side_effect = [mockresponse1, mockresponse2]\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(\"foo2\", self.cahandler._cert_id_get(10))\n        self.assertIn(\n            \"DEBUG:test_a2c:CAhandler._cert_id_get() waiting for job to complete. Attempt: 0 status: note\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.cert_serial_get\")\n    def test_082__certid_get_from_serial(self, mock_serial, mock_req):\n        \"\"\"_certid_get_from_serial()\"\"\"\n        mock_serial.return_value = \"mock_serial\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"items\": [{\"id\": \"id1\"}, {\"id\": \"id2\"}]}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\"id1\", self.cahandler._certid_get_from_serial(\"cert_raw\"))\n\n    @patch(\"requests.get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.cert_serial_get\")\n    def test_083__certid_get_from_serial(self, mock_serial, mock_req):\n        \"\"\"_certid_get_from_serial()\"\"\"\n        mock_serial.return_value = \"mock_serial\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"items\": [{\"di\": \"id1\"}, {\"id\": \"id2\"}]}\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._certid_get_from_serial(\"cert_raw\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to retrieve certificate by serial: mock_serial\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.cert_serial_get\")\n    def test_084__certid_get_from_serial(self, mock_serial, mock_req):\n        \"\"\"_certid_get_from_serial()\"\"\"\n        mock_serial.return_value = \"mock_serial\"\n        mock_req.side_effect = Exception(\"mock_req\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._certid_get_from_serial(\"cert_raw\"))\n        self.assertIn(\n            \"ERROR:test_a2c:API request to fetch certificates got aborted with err: mock_req\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to retrieve certificate by serial: mock_serial\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._certid_get_from_serial\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.header_info_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_085__cert_id_lookup(self, mock_enc, mock_info, mock_serial):\n        \"\"\"test _cert_id_lookup()\"\"\"\n        mock_enc.return_value = \"mock_enc\"\n        mock_info.return_value = [{\"poll_identifier\": \"poll_identifier\"}]\n        mock_serial.return_value = \"mock_serial\"\n        self.assertEqual(\"poll_identifier\", self.cahandler._cert_id_lookup(\"cert_raw\"))\n        self.assertFalse(mock_serial.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._certid_get_from_serial\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.header_info_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_086__cert_id_lookup(self, mock_enc, mock_info, mock_serial):\n        \"\"\"test _cert_id_lookup()\"\"\"\n        mock_enc.return_value = \"mock_enc\"\n        mock_info.return_value = [{\"poll_identifier\": None}]\n        mock_serial.return_value = \"mock_serial\"\n        self.assertEqual(\"mock_serial\", self.cahandler._cert_id_lookup(\"cert_raw\"))\n        self.assertTrue(mock_serial.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._certid_get_from_serial\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.header_info_get\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_087__cert_id_lookup(self, mock_enc, mock_info, mock_serial):\n        \"\"\"test _cert_id_lookup()\"\"\"\n        mock_enc.return_value = \"mock_enc\"\n        mock_info.return_value = [{\"foo\": \"bar\"}]\n        mock_serial.return_value = \"mock_serial\"\n        self.assertEqual(\"mock_serial\", self.cahandler._cert_id_lookup(\"cert_raw\"))\n        self.assertTrue(mock_serial.called)\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_088__revocation_status_poll(self, mock_req, mock_sleep):\n        \"\"\"test _revocation_status_poll()\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"done\"}\n        mock_req.return_value = mockresponse\n        err_dic = {\"serverinternal\": \"serverinternal\"}\n        self.assertEqual(\n            (200, None, None),\n            self.cahandler._revocation_status_poll(\"cert_id\", err_dic),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_089__revocation_status_poll(self, mock_req, mock_sleep):\n        \"\"\"test _revocation_status_poll()\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"failed\"}\n        mock_req.return_value = mockresponse\n        err_dic = {\"serverinternal\": \"serverinternal\"}\n        self.assertEqual(\n            (500, \"serverinternal\", \"Revocation operation failed: error from API\"),\n            self.cahandler._revocation_status_poll(\"cert_id\", err_dic),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_090__revocation_status_poll(self, mock_req, mock_sleep):\n        \"\"\"test _revocation_status_poll()\"\"\"\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"status\": \"pending\"}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"status\": \"done\"}\n        mock_req.side_effect = [mockresponse2, mockresponse2]\n        err_dic = {\"serverinternal\": \"serverinternal\"}\n        self.assertEqual(\n            (200, None, None),\n            self.cahandler._revocation_status_poll(\"cert_id\", err_dic),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_091__revocation_status_poll(self, mock_req, mock_sleep):\n        \"\"\"test _revocation_status_poll()\"\"\"\n        mockresponse1 = Mock()\n        mockresponse1.json = lambda: {\"status\": \"pending\"}\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"status\": \"failed\"}\n        mock_req.side_effect = [mockresponse2, mockresponse2]\n        err_dic = {\"serverinternal\": \"serverinternal\"}\n        self.assertEqual(\n            (500, \"serverinternal\", \"Revocation operation failed: error from API\"),\n            self.cahandler._revocation_status_poll(\"cert_id\", err_dic),\n        )\n\n    @patch(\"time.sleep\")\n    @patch(\"requests.get\")\n    def test_092__revocation_status_poll(self, mock_req, mock_sleep):\n        \"\"\"test _revocation_status_poll()\"\"\"\n        mockresponse = Mock()\n        mockresponse.json = lambda: {\"status\": \"pending\"}\n        mock_req.return_value = mockresponse\n        err_dic = {\"serverinternal\": \"serverinternal\"}\n        self.assertEqual(\n            (500, \"serverinternal\", \"Revocation operation failed: Timeout\"),\n            self.cahandler._revocation_status_poll(\"cert_id\", err_dic),\n        )\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_093_enroll(\n        self, mock_recode, mock_policy, mock_template, mock_enroll, mock_ecl\n    ):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = \"policylink_id\"\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": \"id\"}\n        self.assertEqual(\n            (\"error\", \"bundle\", \"raw\", \"cert_id\"), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_policy.called)\n        self.assertTrue(mock_template.called)\n        self.assertTrue(mock_enroll.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_094_enroll(\n        self, mock_recode, mock_policy, mock_template, mock_enroll, mock_ecl\n    ):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = \"policylink_id\"\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        self.cahandler.enrollment_config_log = True\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": \"id\"}\n        self.assertEqual(\n            (\"error\", \"bundle\", \"raw\", \"cert_id\"), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_policy.called)\n        self.assertTrue(mock_template.called)\n        self.assertTrue(mock_enroll.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_095_enroll(self, mock_recode, mock_policy, mock_template, mock_enroll):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = \"policylink_id\"\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": None}\n        self.assertEqual(\n            (\n                'ID lookup for container\"name\" failed.',\n                None,\n                None,\n                None,\n            ),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertFalse(mock_policy.called)\n        self.assertFalse(mock_template.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_096_enroll(self, mock_recode, mock_policy, mock_template, mock_enroll):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = None\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": \"id\"}\n        self.assertEqual(\n            (\"Enrollment aborted. ca: None, tsg_id: id\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_policy.called)\n        self.assertFalse(mock_template.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_097_enroll(self, mock_recode, mock_policy, mock_template, mock_enroll):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = None\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": \"id\"}\n        self.cahandler.error = \"error\"\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_recode.called)\n        self.assertFalse(mock_policy.called)\n        self.assertFalse(mock_template.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_enroll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._template_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._ca_policylink_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.b64_url_recode\")\n    def test_098_enroll(\n        self, mock_recode, mock_policy, mock_template, mock_enroll, mock_eab\n    ):\n        \"\"\"test enroll\"\"\"\n        mock_recode.return_value = \"csr\"\n        mock_policy.return_value = \"policylink_id\"\n        mock_template.return_value = \"template_id\"\n        mock_enroll.return_value = (\"error\", \"bundle\", \"raw\", \"cert_id\")\n        mock_eab.return_value = \"eab\"\n        self.cahandler.template_info_dic = {\"name\": \"name\", \"id\": None}\n        self.cahandler.container_info_dic = {\"name\": \"name\", \"id\": \"id\"}\n        self.assertEqual((\"eab\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_policy.called)\n        self.assertTrue(mock_template.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._revocation_status_poll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.error_dic_get\")\n    def test_099_revoke(self, mock_err, mock_idl, mock_post, mock_poll):\n        \"\"\"test revoke\"\"\"\n        mock_err.return_value = {\"foo\": \"bar\", \"serverinternal\": \"serverinternal\"}\n        mock_idl.return_value = \"cert_id\"\n        mock_post.return_value = {\"urls\": {\"job\": \"foo/v2/jobs/\"}}\n        mock_poll.return_value = (200, \"message\", \"detail\")\n        self.assertEqual((200, \"message\", \"detail\"), self.cahandler.revoke(\"cert_raw\"))\n        self.assertTrue(mock_err.called)\n        self.assertTrue(mock_idl.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_poll.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._revocation_status_poll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.error_dic_get\")\n    def test_100_revoke(self, mock_err, mock_idl, mock_post, mock_poll, mock_eab):\n        \"\"\"test revoke\"\"\"\n        mock_err.return_value = {\"foo\": \"bar\", \"serverinternal\": \"serverinternal\"}\n        mock_idl.return_value = \"cert_id\"\n        mock_post.return_value = {\"urls\": {\"foo\": \"foo\"}}\n        mock_poll.return_value = (200, \"message\", \"detail\")\n        self.assertEqual(\n            (500, \"serverinternal\", \"Revocation operation failed\"),\n            self.cahandler.revoke(\"cert_raw\"),\n        )\n        self.assertTrue(mock_err.called)\n        self.assertTrue(mock_idl.called)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_poll.called)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._revocation_status_poll\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._api_post\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._cert_id_lookup\")\n    @patch(\"examples.ca_handler.nclm_ca_handler.error_dic_get\")\n    def test_101_revoke(self, mock_err, mock_idl, mock_post, mock_poll, mock_eab):\n        \"\"\"test revoke\"\"\"\n        mock_err.return_value = {\"foo\": \"bar\", \"serverinternal\": \"serverinternal\"}\n        mock_idl.return_value = \"cert_id\"\n        mock_post.return_value = {\"urls\": {\"foo\": \"foo\"}}\n        mock_poll.return_value = (200, \"message\", \"detail\")\n        self.cahandler.eab_profiling = True\n        self.assertEqual(\n            (500, \"serverinternal\", \"Revocation operation failed\"),\n            self.cahandler.revoke(\"cert_raw\"),\n        )\n        self.assertTrue(mock_err.called)\n        self.assertTrue(mock_idl.called)\n        self.assertTrue(mock_post.called)\n        self.assertFalse(mock_poll.called)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.nclm_ca_handler.CAhandler._config_check\")\n    def test_102_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        self.cahandler.error = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    if os.path.exists(\"acme_test.db\"):\n        os.remove(\"acme_test.db\")\n    unittest.main()\n"
  },
  {
    "path": "test/test_nonce.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nfrom unittest.mock import patch, MagicMock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.nonce import Nonce\n\n        self.nonce = Nonce(False, self.logger)\n\n    def test_001_generate_nonce_value(self):\n        \"\"\"test Nonce._generate_nonce_value() and check if we get something back\"\"\"\n        self.assertIsNotNone(self.nonce._generate_nonce_value())\n\n    def test_002_generate_and_add(self):\n        \"\"\"test Nonce._generate_and_add() and check if we get something back\"\"\"\n        self.assertIsNotNone(self.nonce.generate_and_add())\n\n    def test_003_nonce_check(self):\n        \"\"\"test Nonce.check() with missing nonce\"\"\"\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:badNonce\", \"NONE\"),\n            self.nonce.check({\"foo\": \"bar\"}),\n        )\n\n    @patch(\"acme_srv.nonce.Nonce._validate_and_consume_nonce\")\n    def test_004_nonce_check(self, mock_validate_and_consume_nonce):\n        \"\"\"test Nonce.check() calls _validate_and_consume_nonce()\"\"\"\n        mock_validate_and_consume_nonce.return_value = (200, None, None)\n        self.assertEqual((200, None, None), self.nonce.check({\"nonce\": \"aaa\"}))\n\n    @patch(\"acme_srv.nonce.DBstore\")\n    def test_005_nonce__validate_and_consume_nonce(self, mock_dbstore_class):\n        \"\"\"test Nonce._validate_and_consume_nonce()\"\"\"\n        # Setup mock to return True for nonce_check\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.nonce_check.return_value = True\n        mock_dbstore_instance.nonce_delete.return_value = None\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Create a new nonce instance with the mocked dbstore\n        from acme_srv.nonce import Nonce\n\n        nonce = Nonce(False, self.logger)\n\n        self.assertEqual((200, None, None), nonce._validate_and_consume_nonce(\"aaa\"))\n\n    @patch(\"acme_srv.nonce.DBstore\")\n    def test_006_nonce_generate_and_add(self, mock_dbstore_class):\n        \"\"\"test Nonce._add() if dbstore.nonce_add raises an exception\"\"\"\n        # Setup mock to raise exception\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.nonce_add.side_effect = Exception(\"exc_nonce_add\")\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Create a new nonce instance with the mocked dbstore\n        from acme_srv.nonce import Nonce\n\n        nonce = Nonce(False, self.logger)\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            nonce.generate_and_add()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to add new nonce: exc_nonce_add\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.nonce.DBstore\")\n    def test_007_nonce__validate_and_consume_nonce(self, mock_dbstore_class):\n        \"\"\"test Nonce._validate_and_consume_nonce() if dbstore.nonce_delete raises an exception\"\"\"\n        # Setup mock: nonce_check returns True, nonce_delete raises exception\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.nonce_check.return_value = True\n        mock_dbstore_instance.nonce_delete.side_effect = Exception(\"exc_nonce_delete\")\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Create a new nonce instance with the mocked dbstore\n        from acme_srv.nonce import Nonce\n\n        nonce = Nonce(False, self.logger)\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            nonce._validate_and_consume_nonce(\"nonce\")\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to delete nonce: exc_nonce_delete\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.nonce.DBstore\")\n    def test_008_nonce__validate_and_consume_nonce(self, mock_dbstore_class):\n        \"\"\"test Nonce._validate_and_consume_nonce() if dbstore.nonce_check raises an exception\"\"\"\n        # Setup mock to raise exception on nonce_check\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.nonce_check.side_effect = Exception(\"exc_nonce_check\")\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Create a new nonce instance with the mocked dbstore\n        from acme_srv.nonce import Nonce\n\n        nonce = Nonce(False, self.logger)\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            nonce._validate_and_consume_nonce(\"nonce\")\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to check nonce: exc_nonce_check\",\n            lcm.output,\n        )\n\n    def test_009__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.nonce.__enter__()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_openssl_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nimport configparser\nimport datetime\nfrom unittest.mock import patch, mock_open, Mock\nfrom OpenSSL import crypto\nimport hashlib\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\ndef convert_string_to_byte(value):\n    \"\"\"convert a variable to byte if needed\"\"\"\n    if hasattr(value, \"encode\"):\n        result = value.encode()\n    else:\n        result = value\n    return result\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.openssl_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002_check_config(self):\n        \"\"\"CAhandler._config_check with an empty config_dict\"\"\"\n        self.cahandler.issuer_dict = {}\n        self.assertEqual(\n            \"issuing_ca_key not specfied in config_file\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_003_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with key in config_dict but not existing\"\"\"\n        self.cahandler.issuer_dict = {\"issuing_ca_key\": \"foo.pem\"}\n        mock_file.side_effect = [False]\n        self.assertEqual(\n            \"issuing_ca_key foo.pem does not exist\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_004_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with key in config_dict key is existing\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\"\n        }\n        mock_file.side_effect = [True]\n        self.assertEqual(\n            \"issuing_ca_cert must be specified in config file\",\n            self.cahandler._config_check(),\n        )\n\n    @patch(\"os.path.exists\")\n    def test_005_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with key and cert in config_dict but cert does not exist\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": \"bar\",\n        }\n        mock_file.side_effect = [True, False]\n        self.assertEqual(\n            \"issuing_ca_cert bar does not exist\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_006_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check withoutissuing_ca_crl in config_dic\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n        }\n        mock_file.side_effect = [True, True]\n        self.assertEqual(\n            \"issuing_ca_crl must be specified in config file\",\n            self.cahandler._config_check(),\n        )\n\n    @patch(\"os.path.exists\")\n    def test_007_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with wrong CRL in config_dic\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": \"foo.pem\",\n        }\n        mock_file.side_effect = [True, True, False]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"cert_save_path must be specified in config file\",\n                self.cahandler._config_check(),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Issuing_ca_crl foo.pem does not exist.\",\n            lcm.output,\n        )\n\n    @patch(\"os.path.exists\")\n    def test_008_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check without cert save path\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/sub-ca-crl.pem\",\n        }\n        mock_file.side_effect = [True, True, True]\n        self.assertEqual(\n            \"cert_save_path must be specified in config file\",\n            self.cahandler._config_check(),\n        )\n\n    @patch(\"os.path.exists\")\n    def test_009_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with key and cert in config_dict\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/sub-ca-crl.pem\",\n        }\n        self.cahandler.cert_save_path = \"foo\"\n        mock_file.side_effect = [True, True, True, False]\n        self.assertEqual(\n            \"cert_save_path foo does not exist\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_010_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check completed\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/sub-ca-crl.pem\",\n        }\n        self.cahandler.cert_save_path = self.dir_path + \"/ca/certs\"\n        self.cahandler.ca_cert_chain_list = [\"foo\", \"bar\"]\n        mock_file.side_effect = [True, True, True, True]\n        self.assertFalse(self.cahandler._config_check())\n\n    @patch(\"os.path.exists\")\n    def test_011_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with wrong openssl.conf\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/sub-ca-crl.pem\",\n        }\n        self.cahandler.cert_save_path = self.dir_path + \"/ca/certs\"\n        self.cahandler.ca_cert_chain_list = [\"foo\", \"bar\"]\n        self.cahandler.openssl_conf = \"foo\"\n        mock_file.side_effect = [True, True, True, True, False]\n        self.assertEqual(\n            \"openssl_conf foo does not exist\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_012_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check with openssl.conf completed successfully\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/sub-ca-crl.pem\",\n        }\n        self.cahandler.cert_save_path = self.dir_path + \"/ca/certs\"\n        self.cahandler.ca_cert_chain_list = [\"foo\", \"bar\"]\n        self.cahandler.openssl_conf = self.dir_path + \"/ca/fr1.txt\"\n        mock_file.side_effect = [True, True, True, True, True]\n        self.assertFalse(self.cahandler._config_check())\n\n    def test_013_generate_pem_chain(self):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert but no ca cert\"\"\"\n        self.assertEqual(\n            \"ee-cert\", self.cahandler._pemcertchain_generate(\"ee-cert\", None)\n        )\n\n    def test_014_generate_pem_chain(self):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert and ca cert\"\"\"\n        self.assertEqual(\n            \"ee-certca-cert\",\n            self.cahandler._pemcertchain_generate(\"ee-cert\", \"ca-cert\"),\n        )\n\n    def test_015_generate_pem_chain(self):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert ca and an invalit entry in cert_cain_list cert\"\"\"\n        self.cahandler.ca_cert_chain_list = [\"foo.pem\"]\n        self.assertEqual(\n            \"ee-certca-cert\",\n            self.cahandler._pemcertchain_generate(\"ee-cert\", \"ca-cert\"),\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"_fakeroot-cert-1\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_016_generate_pem_chain(self, mock_file):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert ca and an valid entry in cert_cain_list cert\"\"\"\n        self.cahandler.ca_cert_chain_list = [self.dir_path + \"/ca/fr1.txt\"]\n        mock_file.return_value = True\n        mock_open.return_vlaue = \"foo\"\n        self.assertEqual(\n            \"ee-cert_ca-cert_fakeroot-cert-1\",\n            self.cahandler._pemcertchain_generate(\"ee-cert\", \"_ca-cert\"),\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"_fakeroot-cert-1\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_017_generate_pem_chain(self, mock_file):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert ca and two valid entry in cert_cain_list\"\"\"\n        self.cahandler.ca_cert_chain_list = [\n            self.dir_path + \"/ca/fr1.txt\",\n            self.dir_path + \"/ca/fr2.txt\",\n        ]\n        mock_file.side_effect = [True, True]\n        self.assertEqual(\n            \"ee-cert_ca-cert_fakeroot-cert-1_fakeroot-cert-1\",\n            self.cahandler._pemcertchain_generate(\"ee-cert\", \"_ca-cert\"),\n        )\n\n    @patch(\"builtins.open\", mock_open(read_data=\"_fakeroot-cert-1\"), create=True)\n    @patch(\"os.path.exists\")\n    def test_018_generate_pem_chain(self, mock_file):\n        \"\"\"CAhandler._pemcertchain_generate with EE cert ca and two valid entry in cert_cain_list and two invalid entriest\"\"\"\n        self.cahandler.ca_cert_chain_list = [\n            self.dir_path + \"/ca/fr1.txt\",\n            \"foo1\",\n            self.dir_path + \"/ca/fr2.txt\",\n            \"foo2\",\n        ]\n        mock_file.side_effect = [True, False, True, False]\n        self.assertEqual(\n            \"ee-cert_ca-cert_fakeroot-cert-1_fakeroot-cert-1\",\n            self.cahandler._pemcertchain_generate(\"ee-cert\", \"_ca-cert\"),\n        )\n\n    def test_019_load_ca_key_cert(self):\n        \"\"\"CAhandler._ca_load() with empty issuer_dict\"\"\"\n        self.cahandler.issuer_dict = {}\n        self.assertEqual((None, None), self.cahandler._ca_load())\n\n    def test_020_load_ca_key_cert(self):\n        \"\"\"CAhandler._ca_load() with issuer_dict containing invalid key\"\"\"\n        self.cahandler.issuer_dict = {\"issuing_ca_key\": \"foo.pem\"}\n        self.assertEqual((None, None), self.cahandler._ca_load())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    def test_021_load_ca_key_cert(self, mock_crypto, mock_file):\n        \"\"\"CAhandler._ca_load() with issuer_dict containing valid key\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\"\n        }\n        mock_crypto.return_value = \"foo\"\n        mock_file.return_value = True\n        self.assertEqual((\"foo\", None), self.cahandler._ca_load())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"cryptography.x509.load_pem_x509_certificate\")\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    def test_022_load_ca_key_cert(self, mock_crypto_key, mock_crypto_cert, mock_file):\n        \"\"\"CAhandler._ca_load() with issuer_dict containing key and passphrase\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n        }\n        mock_crypto_cert.return_value = \"cert\"\n        mock_crypto_key.return_value = \"key\"\n        mock_file.return_value = True\n        self.assertEqual((\"key\", None), self.cahandler._ca_load())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"cryptography.x509.load_pem_x509_certificate\")\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    def test_023_load_ca_key_cert(self, mock_crypto_key, mock_crypto_cert, mock_file):\n        \"\"\"CAhandler._ca_load() with issuer_dict containing key and invalid cert\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n            \"issuing_ca_cert\": \"foo.pem\",\n        }\n        mock_crypto_cert.return_value = None\n        mock_crypto_key.return_value = \"key\"\n        mock_file.return_value = True\n        self.assertEqual((\"key\", None), self.cahandler._ca_load())\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"cryptography.x509.load_pem_x509_certificate\")\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    def test_024_load_ca_key_cert(self, mock_crypto_key, mock_crypto_cert, mock_file):\n        \"\"\"CAhandler._ca_load() with issuer_dict containing key and invalid cert\"\"\"\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n        }\n        mock_crypto_key.return_value = \"foo\"\n        mock_crypto_cert.return_value = \"bar\"\n        mock_file.return_value = True\n        self.assertEqual((\"foo\", \"bar\"), self.cahandler._ca_load())\n\n    def test_025_revocation(self):\n        \"\"\"revocation without having a CRL in issuer_dic\"\"\"\n        with open(self.dir_path + \"/ca/sub-ca-client.txt\", \"r\") as fso:\n            cert = fso.read()\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"Unsupported operation\"),\n            self.cahandler.revoke(cert),\n        )\n\n    def test_026_revocation(self):\n        \"\"\"revocation without having a CRL in issuer_dic but none\"\"\"\n        self.cahandler.issuer_dict = {\"crl\": None}\n        with open(self.dir_path + \"/ca/sub-ca-client.txt\", \"r\") as fso:\n            cert = fso.read()\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"Unsupported operation\"),\n            self.cahandler.revoke(cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    def test_027_revocation(self, mock_ca_load, mock_serial):\n        \"\"\"revocation cert no CA key\"\"\"\n        with open(self.dir_path + \"/ca/sub-ca-client.txt\", \"r\") as fso:\n            cert = fso.read()\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/foo-ca-crl.pem\",\n        }\n        mock_ca_load.return_value = (None, \"ca_cert\")\n        mock_serial.return_value = \"serial\"\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"configuration error\"),\n            self.cahandler.revoke(cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    def test_028_revocation(self, mock_ca_load, mock_serial):\n        \"\"\"revocation cert no CA cert\"\"\"\n        with open(self.dir_path + \"/ca/sub-ca-client.txt\", \"r\") as fso:\n            cert = fso.read()\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/foo-ca-crl.pem\",\n        }\n        mock_ca_load.return_value = (\"ca_key\", None)\n        mock_serial.return_value = \"serial\"\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"configuration error\"),\n            self.cahandler.revoke(cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    def test_029_revocation(self, mock_ca_load, mock_serial):\n        \"\"\"revocation cert no serial\"\"\"\n        with open(self.dir_path + \"/ca/sub-ca-client.txt\", \"r\") as fso:\n            cert = fso.read()\n        self.cahandler.issuer_dict = {\n            \"issuing_ca_key\": self.dir_path + \"/ca/sub-ca-key.pem\",\n            \"passphrase\": \"Test1234\",\n            \"issuing_ca_cert\": self.dir_path + \"/ca/sub-ca-cert.pem\",\n            \"issuing_ca_crl\": self.dir_path + \"/ca/foo-ca-crl.pem\",\n        }\n        mock_ca_load.return_value = (\"ca_key\", \"ca_cert\")\n        mock_serial.return_value = None\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"configuration error\"),\n            self.cahandler.revoke(cert),\n        )\n\n    def test_030_list_check(self):\n        \"\"\"CAhandler._list_check failed check as empty entry\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = None\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_031_list_check(self):\n        \"\"\"CAhandler._list_check check against empty list\"\"\"\n        list_ = []\n        entry = \"host.bar.foo\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_032_list_check(self):\n        \"\"\"CAhandler._list_check successful check against 1st element of a list\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_033_list_check(self):\n        \"\"\"CAhandler._list_check unsuccessful as endcheck failed\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo.bar_\"\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_034_list_check(self):\n        \"\"\"CAhandler._list_check successful without $\"\"\"\n        list_ = [\"bar.foo\", \"foo.bar$\"]\n        entry = \"host.bar.foo.bar_\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_035_list_check(self):\n        \"\"\"CAhandler._list_check wildcard check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_036_list_check(self):\n        \"\"\"CAhandler._list_check failed wildcard check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"*.bar.foo_\"\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_037_list_check(self):\n        \"\"\"CAhandler._list_check not end check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo gna\"\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_038_list_check(self):\n        \"\"\"CAhandler._list_check $ at the end\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"bar.foo$\"\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_039_list_check(self):\n        \"\"\"CAhandler._list_check check against empty list flip\"\"\"\n        list_ = []\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.cahandler._list_check(entry, list_, True))\n\n    def test_040_list_check(self):\n        \"\"\"CAhandler._list_check flip successful check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.cahandler._list_check(entry, list_, True))\n\n    def test_041_list_check(self):\n        \"\"\"CAhandler._list_check flip unsuccessful check\"\"\"\n        list_ = [\"bar.foo$\", \"foo.bar$\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.cahandler._list_check(entry, list_, True))\n\n    def test_042_list_check(self):\n        \"\"\"CAhandler._list_check unsuccessful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"\\*.bar.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(self.cahandler._list_check(entry, list_))\n\n    def test_043_list_check(self):\n        \"\"\"CAhandler._list_check successful whildcard check\"\"\"\n        list_ = [\"foo.bar$\", r\"\\*.bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_044_list_check(self):\n        \"\"\"CAhandler._list_check successful whildcard in list but not in string\"\"\"\n        list_ = [\"foo.bar$\", \"*.bar.foo\"]\n        entry = \"foo.bar.foo\"\n        self.assertTrue(self.cahandler._list_check(entry, list_))\n\n    def test_045_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check against empty lists\"\"\"\n        white_list = []\n        black_list = []\n        entry = \"host.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_046_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check against empty whitlist but match in blocked_domainlist\"\"\"\n        white_list = []\n        black_list = [\"host.bar.foo$\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_047_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check against empty whitlist but no match in blocked_domainlist\"\"\"\n        white_list = []\n        black_list = [\"faulty.bar.foo$\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_048_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check against empty whitlist wildcard check does not hit\"\"\"\n        white_list = []\n        black_list = [r\"\\*.bar.foo$\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_049_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check against empty whitlist wildcard check hit\"\"\"\n        white_list = []\n        black_list = [r\"\\*.bar.foo$\"]\n        entry = \"*.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_050_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check successful wl check with empty bl\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = []\n        entry = \"host.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_051_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check unsuccessful empty bl\"\"\"\n        white_list = [\"foo.foo$\", \"host.bar.foo$\"]\n        black_list = []\n        entry = \"host.bar.foo.bar\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_052_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check unsuccessful host in bl\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"host.bar.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_053_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check unsuccessful host in bl but not on first position\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"foo.bar$\", \"host.bar.foo\", \"foo.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_054_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check successful wildcard in entry not n bl\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"host.bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_055_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check successful wildcard blocked_domainlisting - no match\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [r\"\\*.bar.foo\"]\n        entry = \"host.bar.foo\"\n        self.assertTrue(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_056_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check  failed wildcard black-listing\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [r\"\\*.bar.foo\"]\n        entry = \"*.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_057_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check faked domain\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"google.com.bar.foo\"]\n        entry = \"foo.google.com.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_058_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check faked wc domain\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"google.com.bar.foo$\"]\n        entry = \"*.google.com.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_059_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check faked domain\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"google.com\"]\n        entry = \"*.google.com.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    def test_060_string_wlbl_check(self):\n        \"\"\"CAhandler._string_wlbl_check faked hostname and domain\"\"\"\n        white_list = [\"foo.foo\", \"bar.foo$\"]\n        black_list = [\"google.com\"]\n        entry = \"www.google.com.bar.foo\"\n        self.assertFalse(\n            self.cahandler._string_wlbl_check(entry, white_list, black_list)\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_061_csr_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr with empty allowed_domainlist and blocked_domainlists\"\"\"\n        self.cahandler.allowed_domainlist = []\n        self.cahandler.blocked_domainlist = []\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        csr = \"csr\"\n        self.assertEqual((True, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._string_wlbl_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_062_csr_check(self, mock_san, mock_cn, mock_lcheck):\n        \"\"\"CAhandler._check_csr with list and failed check\"\"\"\n        self.cahandler.allowed_domainlist = [\"foo.bar\"]\n        self.cahandler.blocked_domainlist = []\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        mock_lcheck.side_effect = [True, False]\n        csr = \"csr\"\n        self.assertEqual((False, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._string_wlbl_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_063_csr_check(self, mock_san, mock_cn, mock_lcheck):\n        \"\"\"CAhandler._check_csr with list and successful check\"\"\"\n        self.cahandler.allowed_domainlist = [\"foo.bar\"]\n        self.cahandler.blocked_domainlist = []\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        mock_lcheck.side_effect = [True, True]\n        csr = \"csr\"\n        self.assertEqual((True, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._string_wlbl_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_064_csr_check(self, mock_san, mock_cn, mock_lcheck):\n        \"\"\"CAhandler._check_csr san parsing failed\"\"\"\n        self.cahandler.allowed_domainlist = [\"foo.bar\"]\n        self.cahandler.blocked_domainlist = []\n        mock_san.return_value = [\"host.google.com\"]\n        mock_cn.return_value = \"host2.foo.bar\"\n        mock_lcheck.side_effect = [True, True]\n        csr = \"csr\"\n        self.assertEqual((False, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_065_csr_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr san parsing failed\"\"\"\n        self.cahandler.allowed_domainlist = [\"foo.bar\"]\n        self.cahandler.blocked_domainlist = []\n        mock_san.return_value = []\n        mock_cn.return_value = None\n        csr = \"csr\"\n        self.assertEqual((False, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_066_csr_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr cn_enforce\"\"\"\n        mock_san.return_value = [\"DNS:host.foo.bar\"]\n        mock_cn.return_value = None\n        csr = \"csr\"\n        self.assertEqual((True, \"host.foo.bar\"), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_cn_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.csr_san_get\")\n    def test_067_csr_check(self, mock_san, mock_cn):\n        \"\"\"CAhandler._check_csr cn_enforce  but no san\"\"\"\n        mock_san.return_value = []\n        mock_cn.return_value = None\n        csr = \"csr\"\n        self.assertEqual((True, None), self.cahandler._csr_check(csr))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_load\")\n    def test_068__enter__(self, mock_cfg):\n        \"\"\"test enter\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    def test_069_trigger(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_070_poll(self):\n        \"\"\"test poll\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_071_certificate_store(self):\n        \"\"\"_certificate_store()\"\"\"\n        cert = Mock()\n        cert.get_serial_number = Mock(return_value=42)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._certificate_store(cert)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate storage failed: cert_save_path is missing in the handler configuration.\",\n            lcm.output,\n        )\n\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"))\n    @patch(\"os.mkdir\")\n    @patch(\"os.path.isdir\")\n    def test_072_certificate_store(self, mock_os, mock_mkdir, mock_dump):\n        \"\"\"_certificate_store()\"\"\"\n        mock_os.return_value = True\n        mock_mkdir.return_value = Mock()\n        cert = Mock()\n        cert.serial_number = 42\n        self.cahandler.cert_save_path = \"template\"\n        mock_dump.return_value = \"foo\"\n        self.cahandler._certificate_store(cert)\n        self.assertFalse(mock_mkdir.called)\n\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"))\n    @patch(\"os.mkdir\")\n    @patch(\"os.path.isdir\")\n    def test_073_certificate_store(self, mock_os, mock_mkdir, mock_dump):\n        \"\"\"_certificate_store()\"\"\"\n        mock_os.return_value = False\n        mock_mkdir.return_value = Mock()\n        cert = Mock()\n        cert.serial_number = 42\n        self.cahandler.cert_save_path = \"template\"\n        mock_dump.return_value = \"foo\"\n        self.cahandler._certificate_store(cert)\n        self.assertTrue(mock_mkdir.called)\n\n    @patch(\"OpenSSL.crypto.dump_certificate\")\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"))\n    @patch(\"os.mkdir\")\n    @patch(\"os.path.isdir\")\n    def test_074_certificate_store(self, mock_os, mock_mkdir, mock_dump):\n        \"\"\"_certificate_store()\"\"\"\n        mock_os.return_value = True\n        mock_mkdir.return_value = Mock()\n        cert = Mock()\n        cert.serial_number = 42\n        self.cahandler.cert_save_path = \"template\"\n        self.cahandler.save_cert_as_hex = True\n        mock_dump.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.cahandler._certificate_store(cert)\n        self.assertIn(\"DEBUG:test_a2c:Convert serial to hex: 42: 2A\", lcm.output)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_075__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"save_cert_as_hex\": False}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.save_cert_as_hex)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_076__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"save_cert_as_hex\": True}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertTrue(self.cahandler.save_cert_as_hex)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_077__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blocked_domainlist\": \"foo.json\"}\n        mock_load_cfg.return_value = parser\n        mock_jl.return_value = \"blocked_domainlist\"\n        self.cahandler._config_load()\n        self.assertEqual(\"blocked_domainlist\", self.cahandler.blocked_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_078__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blacklist\": \"foo.json\"}\n        mock_load_cfg.return_value = parser\n        mock_jl.return_value = \"blocked_domainlist\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"blocked_domainlist\", self.cahandler.blocked_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:Deprecated config: found \"blacklist\". Please rename to \"blocked_domainlist\".',\n            lcm.output,\n        )\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_079__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"whitelist\": \"foo.json\"}\n        mock_load_cfg.return_value = parser\n        mock_jl.return_value = \"allowed_domainlist\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"allowed_domainlist\", self.cahandler.allowed_domainlist)\n        self.assertIn(\n            'ERROR:test_a2c:Deprecated config: found \"whitelist\". Please rename to \"allowed_domainlist\".',\n            lcm.output,\n        )\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_080__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": \"foo.json\"}\n        mock_load_cfg.return_value = parser\n        mock_jl.return_value = \"allowed_domainlist\"\n        self.cahandler._config_load()\n        self.assertEqual(\"allowed_domainlist\", self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_081__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"*.bar.local\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_082__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"allowed_domainlist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        mock_jl.side_effect = Exception(\"mock_jl\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load allowed_domainlist parameter. Block all domains: mock_jl\",\n            lcm.output,\n        )\n        self.assertEqual([\"block.all\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_083__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blocked_domainlist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"*.bar.local\"], self.cahandler.blocked_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_084__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blocked_domainlist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        mock_jl.side_effect = Exception(\"mock_jl\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load blocked_domainlist parameter. Block all domains: mock_jl\",\n            lcm.output,\n        )\n        self.assertEqual([\"block.all\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_085__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"whitelist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"*.bar.local\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_086__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"whitelist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        mock_jl.side_effect = Exception(\"mock_jl\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load whitelist parameter. Block all domains: mock_jl\",\n            lcm.output,\n        )\n        self.assertEqual([\"block.all\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_087__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blacklist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"*.bar.local\"], self.cahandler.blocked_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_088__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"blacklist\": '[\"*.bar.local\"]'}\n        mock_load_cfg.return_value = parser\n        mock_jl.side_effect = Exception(\"mock_jl\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load blacklist parameter. Block all domains: mock_jl\",\n            lcm.output,\n        )\n        self.assertEqual([\"block.all\"], self.cahandler.allowed_domainlist)\n\n    @patch(\"json.loads\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_089__config_load(self, mock_load_cfg, mock_jl):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"openssl_conf\": \"openssl_conf\"}\n        mock_load_cfg.return_value = parser\n        mock_jl.return_value = \"openssl_conf\"\n        self.cahandler._config_load()\n        self.assertEqual(\"openssl_conf\", self.cahandler.openssl_conf)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_090__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_key\": \"issuing_ca_key\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"issuing_ca_key\", self.cahandler.issuer_dict[\"issuing_ca_key\"])\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_091__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_cert\": \"issuing_ca_cert\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\n            \"issuing_ca_cert\", self.cahandler.issuer_dict[\"issuing_ca_cert\"]\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_092__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_key_passphrase\": \"issuing_ca_key_passphrase\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\n            b\"issuing_ca_key_passphrase\", self.cahandler.issuer_dict[\"passphrase\"]\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_093__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_validity_days\": 10}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(10, self.cahandler.cert_validity_days)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_094__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_save_path\": \"cert_save_path\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"cert_save_path\", self.cahandler.cert_save_path)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_095__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_cert_chain_list\": '[\"root_ca\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"root_ca\"], self.cahandler.ca_cert_chain_list)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_096__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_cert_chain_list\": '[\"root_ca\", \"sub_ca\"]'}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual([\"root_ca\", \"sub_ca\"], self.cahandler.ca_cert_chain_list)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_097__config_load(self, mock_load_cfg):\n        \"\"\"config load\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_crl\": \"issuing_ca_crl\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"issuing_ca_crl\", self.cahandler.issuer_dict[\"issuing_ca_crl\"])\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_098_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with passphrase variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_key_passphrase_variable\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(b\"foo_var\", self.cahandler.issuer_dict[\"passphrase\"])\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_099_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template passpharese variable configured but does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_key_passphrase_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Unable to load issuing_ca_key_passphrase_variable from environment: 'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_100_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with passphrase variable  - overwritten bei cfg file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"issuing_ca_key_passphrase_variable\": \"foo\",\n            \"issuing_ca_key_passphrase\": \"foo_file\",\n        }\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(b\"foo_file\", self.cahandler.issuer_dict[\"passphrase\"])\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite issuing_ca_key_passphrase_variable\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_101__config_load(self, mock_load_cfg):\n        \"\"\"config load no cn_enforce\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.cn_enforce)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_102__config_load(self, mock_load_cfg):\n        \"\"\"config load cn_enforce True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cn_enforce\": True}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertTrue(self.cahandler.cn_enforce)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_103__config_load(self, mock_load_cfg):\n        \"\"\"config load cn_enforce True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cn_enforce\": False}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertFalse(self.cahandler.cn_enforce)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_104__config_load(self, mock_load_cfg):\n        \"\"\"config load cn_enforce True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cn_enforce\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.cn_enforce)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not parse cn_enforce from config file.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_105__config_load(self, mock_load_cfg):\n        \"\"\"config load cn_enforce True\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_validity_adjust\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.cn_enforce)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not parse cert_validity_adjust from config file.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_106___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \"bar\"}}\n        result = {\"foo\": {\"critical\": False, \"value\": \"bar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_107___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \"bar, foobar\"}}\n        result = {\"foo\": {\"critical\": False, \"value\": \"bar, foobar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_108___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \"bar\", \"foo1\": \"bar1\"}}\n        result = {\n            \"foo\": {\"critical\": False, \"value\": \"bar\"},\n            \"foo1\": {\"critical\": False, \"value\": \"bar1\"},\n        }\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_109___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \"critical, bar\"}}\n        result = {\"foo\": {\"critical\": True, \"value\": \"bar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_110___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \" bar, foobar\"}}\n        result = {\"foo\": {\"critical\": False, \"value\": \"bar, foobar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_111___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \" bar, issuer:\"}}\n        result = {\"foo\": {\"critical\": False, \"issuer\": True, \"value\": \"bar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.load_config\")\n    def test_112___certificate_extensions_load(self, mock_load_cfg):\n        \"\"\"extension list load - empty list\"\"\"\n        # mock_load_cfg.return_value = {'extensions': {'foo': 'critical, serverAuth'}}\n        mock_load_cfg.return_value = {\"extensions\": {\"foo\": \" bar, subject:\"}}\n        result = {\"foo\": {\"critical\": False, \"subject\": True, \"value\": \"bar\"}}\n        self.assertEqual(result, self.cahandler._certificate_extensions_load())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_113_enroll(self, mock_chk):\n        \"\"\"enroll test error returned from config_check\"\"\"\n        mock_chk.return_value = \"error\"\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n\n    def test_114__cert_extension_ku_parse(self):\n        \"\"\"test _cert_extension_ku_parse()\"\"\"\n        ext = \"\"\n        result = {\n            \"digital_signature\": False,\n            \"content_commitment\": False,\n            \"key_encipherment\": False,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        self.assertEqual(result, self.cahandler._cert_extension_ku_parse(ext))\n\n    def test_115__cert_extension_ku_parse(self):\n        \"\"\"test _cert_extension_ku_parse()\"\"\"\n        ext = \"digitalSignature\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": False,\n            \"key_encipherment\": False,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        self.assertEqual(result, self.cahandler._cert_extension_ku_parse(ext))\n\n    def test_116__cert_extension_ku_parse(self):\n        \"\"\"test _cert_extension_ku_parse()\"\"\"\n        ext = \"critical, digitalSignature\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": False,\n            \"key_encipherment\": False,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        self.assertEqual(result, self.cahandler._cert_extension_ku_parse(ext))\n\n    def test_117__cert_extension_ku_parse(self):\n        \"\"\"test _cert_extension_ku_parse()\"\"\"\n        ext = \"critical, digitalSignature,keyEncipherment\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": False,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        self.assertEqual(result, self.cahandler._cert_extension_ku_parse(ext))\n\n    def test_118__cert_extension_ku_parse(self):\n        \"\"\"test _cert_extension_ku_parse()\"\"\"\n        ext = \"critical, digitalSignature,keyEncipherment, nonRepudiation\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": True,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        self.assertEqual(result, self.cahandler._cert_extension_ku_parse(ext))\n\n    def test_119__cert_extension_eku_parse(self):\n        \"\"\"test _cert_extension_eku_parse()\"\"\"\n        extension = \"ekeyuse\"\n        self.assertEqual(\n            [\"eKeyUse\"], self.cahandler._cert_extension_eku_parse(extension)\n        )\n\n    def test_120__cert_extension_eku_parse(self):\n        \"\"\"test _cert_extension_eku_parse()\"\"\"\n        extension = \"ekeyUSE\"\n        self.assertEqual(\n            [\"eKeyUse\"], self.cahandler._cert_extension_eku_parse(extension)\n        )\n\n    def test_121__cert_extension_eku_parse(self):\n        \"\"\"test _cert_extension_eku_parse()\"\"\"\n        extension = \"ekeyUSE, ekeyUSE\"\n        self.assertEqual(\n            [\"eKeyUse\", \"eKeyUse\"], self.cahandler._cert_extension_eku_parse(extension)\n        )\n\n    def test_122__cert_extension_eku_parse(self):\n        \"\"\"test _cert_extension_eku_parse()\"\"\"\n        extension = \"ekeyUSE, unknown\"\n        self.assertEqual(\n            [\"eKeyUse\"], self.cahandler._cert_extension_eku_parse(extension)\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_123__cert_extension_dic_parse(\n        self, mock_bc, mock_ku, mock_ski, mock_aki, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        cert_extension_dic = {\n            \"basicConstraints\": {\"critical\": False, \"value\": \"CA:TRUE, pathlen:0\"}\n        }\n        result = [{\"critical\": False, \"name\": \"mock_bc\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertFalse(mock_ski.called)\n        self.assertFalse(mock_aki.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_124__cert_extension_dic_parse(\n        self, mock_bc, mock_ku, mock_ski, mock_aki, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        cert_extension_dic = {\n            \"basicConstraints\": {\"critical\": True, \"value\": \"CA:TRUE, pathlen:0\"}\n        }\n        result = [{\"critical\": True, \"name\": \"mock_bc\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_125__cert_extension_dic_parse(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        cert_extension_dic = {\n            \"subjectKeyIdentifier\": {\"critical\": True, \"value\": \"value\"}\n        }\n        result = [{\"critical\": False, \"name\": \"mock_ski\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_126__cert_extension_dic_parse(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        cert_extension_dic = {\n            \"authorityKeyIdentifier\": {\"critical\": True, \"value\": \"value\"}\n        }\n        result = [{\"critical\": True, \"name\": \"mock_aki\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_127__cert_extension_dic_parse(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        cert_extension_dic = {\"keyUsage\": {\"critical\": True, \"value\": \"value\"}}\n        result = [{\"critical\": True, \"name\": \"mock_ku\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_128__cert_extension_dic_parse(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        cert_extension_dic = {\"extendedKeyUsage\": {\"critical\": True, \"value\": \"value\"}}\n        result = [{\"critical\": True, \"name\": \"mock_eku\"}]\n        self.assertEqual(\n            result,\n            self.cahandler._cert_extension_dic_parse(cert_extension_dic, cert, cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CertificateBuilder\")\n    def test_129__cert_signing_prep(self, mock_builder):\n        \"\"\"test _cert_extension_dic_parse()\"\"\"\n        req = cert = Mock()\n        self.assertTrue(self.cahandler._cert_signing_prep(cert, req, \"subject\"))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_130__cert_extension_default(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_default()\"\"\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_eku\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n        ]\n        self.assertEqual(result, self.cahandler._cert_extension_default(False, False))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_131__cert_extension_default(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_eku\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"name\": \"mock_aki\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._cert_extension_default(cert, False))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_132__cert_extension_default(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_eku\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"name\": \"mock_ski\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._cert_extension_default(False, cert))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.BasicConstraints\")\n    def test_133__cert_extension_default(\n        self, mock_bc, mock_ski, mock_aki, mock_ku, mock_eku\n    ):\n        \"\"\"test _cert_extension_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_eku\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._cert_extension_default(cert, cert))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectAlternativeName\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_default\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_dic_parse\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_extensions_load\"\n    )\n    def test_134__cert_extension_apply(self, mock_cel, mock_cep, mock_ced, mock_san):\n        \"\"\"test _cert_extension_apply()\"\"\"\n\n        mock_cel.return_value = {\"foo\": \"bar\"}\n        mock_cep.return_value = [{\"name\": \"mock_cep\", \"critical\": False}]\n        mock_ced.return_value = [{\"name\": \"mock_ced\", \"critical\": False}]\n        mock_san.return_value = \"mock_san\"\n        cert = Mock()\n        builder = Mock()\n        self.assertTrue(self.cahandler._cert_extension_apply(builder, cert, None))\n        self.assertFalse(mock_cel.called)\n        self.assertFalse(mock_cep.called)\n        self.assertTrue(mock_ced.called)\n        self.assertFalse(mock_san.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectAlternativeName\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_default\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_dic_parse\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_extensions_load\"\n    )\n    def test_135__cert_extension_apply(self, mock_cel, mock_cep, mock_ced, mock_san):\n        \"\"\"test _cert_extension_apply()\"\"\"\n\n        mock_cel.return_value = {\"foo\": \"bar\"}\n        mock_cep.return_value = [{\"name\": \"mock_cep\", \"critical\": False}]\n        mock_ced.return_value = [{\"name\": \"mock_ced\", \"critical\": False}]\n        mock_san.return_value = \"mock_san\"\n        cert = Mock()\n        builder = Mock()\n        self.cahandler.openssl_conf = \"openssl_conf\"\n        self.assertTrue(self.cahandler._cert_extension_apply(builder, cert, None))\n        self.assertTrue(mock_cel.called)\n        self.assertTrue(mock_cep.called)\n        self.assertFalse(mock_ced.called)\n        self.assertFalse(mock_san.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.SubjectAlternativeName\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_default\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_dic_parse\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_extensions_load\"\n    )\n    def test_136__cert_extension_apply(self, mock_cel, mock_cep, mock_ced, mock_san):\n        \"\"\"test _cert_extension_apply()\"\"\"\n        mock_cel.return_value = {\"foo\": \"bar\"}\n        mock_cep.return_value = [{\"name\": \"mock_cep\", \"critical\": False}]\n        mock_ced.return_value = [{\"name\": \"mock_ced\", \"critical\": False}]\n        mock_san.return_value = \"mock_san\"\n        req = Mock()\n        ext1 = Mock()\n        ext1.oid._name = \"subjectAltName\"\n        ext2 = Mock()\n        ext2.oid._name = \"mock_ext\"\n        req.extensions = [ext1, ext2]\n        cert = Mock()\n        builder = Mock()\n        self.assertTrue(self.cahandler._cert_extension_apply(builder, cert, req))\n        self.assertFalse(mock_cel.called)\n        self.assertFalse(mock_cep.called)\n        self.assertTrue(mock_ced.called)\n        self.assertTrue(mock_san.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cacert\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.NameAttribute\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.Name\")\n    @patch(\"base64.b64encode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_store\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_apply\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_signing_prep\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_137_enroll(\n        self,\n        mock_cfgchk,\n        mock_csrchk,\n        mock_recode,\n        mock_bpf,\n        mock_caload,\n        mock_c2b,\n        mock_csrload,\n        mock_csp,\n        mock_csa,\n        mock_store,\n        mock_pem,\n        mock_b2s,\n        mock_b64e,\n        mock_name,\n        mock_nameattr,\n    ):\n        \"\"\"enroll test\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = (True, \"enforce_cn\")\n        mock_caload.return_value = (\"key\", \"cert\")\n        mock_pem.return_value = \"mock_pem\"\n        mock_b2s.return_value = \"mock_b2s\"\n        self.assertEqual(\n            (False, \"mock_pem\", \"mock_b2s\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertTrue(mock_caload.called)\n        self.assertTrue(mock_pem.called)\n        self.assertTrue(mock_b2s.called)\n        self.assertTrue(mock_c2b.called)\n        self.assertTrue(mock_csrload.called)\n        self.assertTrue(mock_csp.called)\n        self.assertTrue(mock_csa.called)\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_bpf.called)\n        self.assertTrue(mock_b64e.called)\n        self.assertFalse(mock_name.called)\n        self.assertFalse(mock_nameattr.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cacert\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.NameAttribute\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.Name\")\n    @patch(\"base64.b64encode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_store\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_apply\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_signing_prep\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_138_enroll(\n        self,\n        mock_cfgchk,\n        mock_csrchk,\n        mock_recode,\n        mock_bpf,\n        mock_caload,\n        mock_c2b,\n        mock_csrload,\n        mock_csp,\n        mock_csa,\n        mock_store,\n        mock_pem,\n        mock_b2s,\n        mock_b64e,\n        mock_name,\n        mock_nameattr,\n    ):\n        \"\"\"enroll test config check failed\"\"\"\n        mock_cfgchk.return_value = \"error\"\n        mock_csrchk.return_value = (True, \"enforce_cn\")\n        mock_caload.return_value = (\"key\", \"cert\")\n        mock_pem.return_value = \"mock_pem\"\n        mock_b2s.return_value = \"mock_b2s\"\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_cfgchk.called)\n        self.assertFalse(mock_csrchk.called)\n        self.assertFalse(mock_caload.called)\n        self.assertFalse(mock_pem.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_c2b.called)\n        self.assertFalse(mock_csrload.called)\n        self.assertFalse(mock_csp.called)\n        self.assertFalse(mock_csa.called)\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_bpf.called)\n        self.assertFalse(mock_b64e.called)\n        self.assertFalse(mock_name.called)\n        self.assertFalse(mock_nameattr.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cacert\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.NameAttribute\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.Name\")\n    @patch(\"base64.b64encode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_store\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_apply\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_signing_prep\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_139_enroll(\n        self,\n        mock_cfgchk,\n        mock_csrchk,\n        mock_recode,\n        mock_bpf,\n        mock_caload,\n        mock_c2b,\n        mock_csrload,\n        mock_csp,\n        mock_csa,\n        mock_store,\n        mock_pem,\n        mock_b2s,\n        mock_b64e,\n        mock_name,\n        mock_nameattr,\n    ):\n        \"\"\"enroll test config check failed\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = (False, \"enforce_cn\")\n        mock_caload.return_value = (\"key\", \"cert\")\n        mock_pem.return_value = \"mock_pem\"\n        mock_b2s.return_value = \"mock_b2s\"\n        self.assertEqual(\n            (\"urn:ietf:params:acme:badCSR\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertFalse(mock_caload.called)\n        self.assertFalse(mock_pem.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_c2b.called)\n        self.assertFalse(mock_csrload.called)\n        self.assertFalse(mock_csp.called)\n        self.assertFalse(mock_csa.called)\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_bpf.called)\n        self.assertFalse(mock_b64e.called)\n        self.assertFalse(mock_name.called)\n        self.assertFalse(mock_nameattr.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cacert\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.NameAttribute\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.Name\")\n    @patch(\"base64.b64encode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_store\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_apply\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_signing_prep\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_140_enroll(\n        self,\n        mock_cfgchk,\n        mock_csrchk,\n        mock_recode,\n        mock_bpf,\n        mock_caload,\n        mock_c2b,\n        mock_csrload,\n        mock_csp,\n        mock_csa,\n        mock_store,\n        mock_pem,\n        mock_b2s,\n        mock_b64e,\n        mock_name,\n        mock_nameattr,\n    ):\n        \"\"\"enroll test config check failed\"\"\"\n        mock_cfgchk.return_value = None\n        mock_csrchk.side_effect = Exception(\"exc_csr_check\")\n        mock_caload.return_value = (\"key\", \"cert\")\n        mock_pem.return_value = \"mock_pem\"\n        mock_b2s.return_value = \"mock_b2s\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Unknown exception\", None, None, None), self.cahandler.enroll(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate enrollment failed due to exception: exc_csr_check\",\n            lcm.output,\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertFalse(mock_caload.called)\n        self.assertFalse(mock_pem.called)\n        self.assertFalse(mock_b2s.called)\n        self.assertFalse(mock_c2b.called)\n        self.assertFalse(mock_csrload.called)\n        self.assertFalse(mock_csp.called)\n        self.assertFalse(mock_csa.called)\n        self.assertFalse(mock_store.called)\n        self.assertFalse(mock_bpf.called)\n        self.assertFalse(mock_b64e.called)\n        self.assertFalse(mock_name.called)\n        self.assertFalse(mock_nameattr.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"cacert\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.NameAttribute\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.Name\")\n    @patch(\"base64.b64encode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certificate_store\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_extension_apply\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_signing_prep\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._csr_check\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._config_check\")\n    def test_141_enroll(\n        self,\n        mock_cfgchk,\n        mock_csrchk,\n        mock_recode,\n        mock_bpf,\n        mock_caload,\n        mock_c2b,\n        mock_csrload,\n        mock_csp,\n        mock_csa,\n        mock_store,\n        mock_pem,\n        mock_b2s,\n        mock_b64e,\n        mock_name,\n        mock_nameattr,\n    ):\n        \"\"\"enroll test\"\"\"\n        mock_cfgchk.return_value = False\n        mock_csrchk.return_value = (True, \"enforce_cn\")\n        mock_caload.return_value = (\"key\", \"cert\")\n        mock_pem.return_value = \"mock_pem\"\n        mock_b2s.return_value = \"mock_b2s\"\n        self.cahandler.cn_enforce = True\n        mock_csrload.return_value.subject = None\n        self.assertEqual(\n            (False, \"mock_pem\", \"mock_b2s\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_cfgchk.called)\n        self.assertTrue(mock_csrchk.called)\n        self.assertTrue(mock_caload.called)\n        self.assertTrue(mock_pem.called)\n        self.assertTrue(mock_b2s.called)\n        self.assertTrue(mock_c2b.called)\n        self.assertTrue(mock_csrload.called)\n        self.assertTrue(mock_csp.called)\n        self.assertTrue(mock_csa.called)\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_bpf.called)\n        self.assertTrue(mock_b64e.called)\n        self.assertTrue(mock_name.called)\n        self.assertTrue(mock_nameattr.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"datetime.datetime\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.RevokedCertificateBuilder\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.x509.CertificateRevocationListBuilder\"\n    )\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_crl\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_now\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_to_date_utc\")\n    def test_142_revoke(\n        self,\n        mock_uts,\n        mock_now,\n        mock_ca_load,\n        mock_serial,\n        mock_crl,\n        mock_certbuilder,\n        mock_revoke,\n        mock_file,\n        mock_datetime,\n    ):\n        \"\"\"test revoke)()\"\"\"\n        self.cahandler.issuer_dict = {\"issuing_ca_crl\": \"issuing_ca_crl\"}\n        mock_ca_load.return_value = (\"ca_key\", \"ca_cert\")\n        mock_serial.return_value = 42\n        mock_crl = Mock()\n        mock_crl.issuer = \"issuer\"\n        mock_file.return_value = True\n        mock_now.return_value = \"now\"\n        mock_datetime.utcnow.return_value.utctimetuple.return_value = \"utcnow\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertIn(\n            \"INFO:test_a2c:Load existing crl issuing_ca_crl)\",\n            lcm.output,\n        )\n        self.assertTrue(mock_ca_load.called)\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_certbuilder.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertTrue(mock_file.called)\n        self.assertTrue(mock_now.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"datetime.datetime\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.RevokedCertificateBuilder\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.x509.CertificateRevocationListBuilder\"\n    )\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_crl\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_now\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_to_date_utc\")\n    def test_143_revoke(\n        self,\n        mock_uts,\n        mock_now,\n        mock_ca_load,\n        mock_serial,\n        mock_crl,\n        mock_certbuilder,\n        mock_revoke,\n        mock_file,\n        mock_datetime,\n    ):\n        \"\"\"test revoke)()\"\"\"\n        self.cahandler.issuer_dict = {\"issuing_ca_crl\": \"issuing_ca_crl\"}\n        mock_ca_load.return_value = (\"ca_key\", Mock())\n        mock_serial.return_value = 42\n        mock_crl = Mock()\n        mock_crl.issuer = \"issuer\"\n        mock_file.return_value = False\n        mock_now.return_value = \"now\"\n        mock_datetime.utcnow.return_value.utctimetuple.return_value = \"utcnow\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((200, None, None), self.cahandler.revoke(\"cert\"))\n        self.assertIn(\n            \"INFO:test_a2c:Create new crl issuing_ca_crl)\",\n            lcm.output,\n        )\n        self.assertTrue(mock_ca_load.called)\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_certbuilder.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertTrue(mock_file.called)\n        self.assertTrue(mock_now.called)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"examples.ca_handler.openssl_ca_handler.isinstance\", return_value=True)\n    @patch(\"datetime.datetime\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.RevokedCertificateBuilder\")\n    @patch(\n        \"examples.ca_handler.openssl_ca_handler.x509.CertificateRevocationListBuilder\"\n    )\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_crl\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_now\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.uts_to_date_utc\")\n    def test_144_revoke(\n        self,\n        mock_uts,\n        mock_now,\n        mock_ca_load,\n        mock_serial,\n        mock_crl,\n        mock_certbuilder,\n        mock_revoke,\n        mock_file,\n        mock_datetime,\n        mock_instance,\n    ):\n        \"\"\"test revoke)()\"\"\"\n        self.cahandler.issuer_dict = {\"issuing_ca_crl\": \"issuing_ca_crl\"}\n        mock_ca_load.return_value = (\"ca_key\", Mock())\n        mock_serial.return_value = 42\n        mock_crl = Mock()\n        mock_crl.issuer = \"issuer\"\n        mock_file.return_value = True\n        mock_now.return_value = \"now\"\n        mock_datetime.utcnow.return_value.utctimetuple.return_value = \"utcnow\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\n                    400,\n                    \"urn:ietf:params:acme:error:alreadyRevoked\",\n                    \"Certificate has already been revoked\",\n                ),\n                self.cahandler.revoke(\"cert\"),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Load existing crl issuing_ca_crl)\",\n            lcm.output,\n        )\n        self.assertTrue(mock_ca_load.called)\n        self.assertTrue(mock_serial.called)\n        self.assertTrue(mock_certbuilder.called)\n        self.assertFalse(mock_revoke.called)\n        self.assertTrue(mock_file.called)\n        self.assertTrue(mock_now.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_145__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.return_value = \"cert1\"\n        mock_exp.return_value = datetime.datetime(2024, 12, 31, 5, 0, 1)\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.return_value = True\n        self.cahandler.ca_cert_chain_list = [\"cacert1\"]\n        self.assertEqual((366, \"cert1\"), self.cahandler._cacert_expiry_get())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_146__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.side_effect = [\"cert1\", \"cert2\"]\n        mock_exp.side_effect = [\n            datetime.datetime(2024, 12, 31, 5, 0, 1),\n            datetime.datetime(2024, 11, 30, 5, 0, 1),\n        ]\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.return_value = True\n        self.cahandler.ca_cert_chain_list = [\"cacert1\", \"cacert2\"]\n        self.assertEqual((335, \"cert2\"), self.cahandler._cacert_expiry_get())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_147__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.side_effect = [\"cert1\", \"cert2\"]\n        mock_exp.side_effect = [\n            datetime.datetime(2024, 10, 30, 5, 0, 1),\n            datetime.datetime(2024, 12, 31, 5, 0, 1),\n        ]\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.return_value = True\n        self.cahandler.ca_cert_chain_list = [\"cacert1\", \"cacert2\"]\n        self.assertEqual((304, \"cert1\"), self.cahandler._cacert_expiry_get())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_148__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.side_effect = [\"cert1\", \"issuing_ca_cert\"]\n        mock_exp.side_effect = [\n            datetime.datetime(2024, 12, 31, 5, 0, 1),\n            datetime.datetime(2024, 11, 30, 5, 0, 1),\n        ]\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.return_value = True\n        self.cahandler.ca_cert_chain_list = [\"cacert1\"]\n        self.cahandler.issuer_dict = {\"issuing_ca_cert\": \"issuing_ca_cert\"}\n        self.assertEqual((335, \"issuing_ca_cert\"), self.cahandler._cacert_expiry_get())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_149__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.side_effect = [\"cert1\", \"issuing_ca_cert\"]\n        mock_exp.side_effect = [\n            datetime.datetime(2024, 10, 30, 5, 0, 1),\n            datetime.datetime(2024, 12, 31, 5, 0, 1),\n        ]\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.return_value = True\n        self.cahandler.ca_cert_chain_list = [\"cacert1\"]\n        self.cahandler.issuer_dict = {\"issuing_ca_cert\": \"issuing_ca_cert\"}\n        self.assertEqual((304, \"cert1\"), self.cahandler._cacert_expiry_get())\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cert_expiry_get\")\n    @patch(\"builtins.open\", mock_open(read_data=\"test\"), create=True)\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.x509.load_pem_x509_certificate\")\n    def test_150__cacert_expiry_get(\n        self, mock_certload, mock_exists, mock_exp, mock_now\n    ):\n        \"\"\"test _cacert_expiry_get()\"\"\"\n        mock_certload.side_effect = [\"cert1\", \"cert2\"]\n        mock_exp.side_effect = [\n            datetime.datetime(2024, 12, 31, 5, 0, 1),\n            datetime.datetime(2024, 11, 30, 5, 0, 1),\n        ]\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_exists.side_effect = [True, False]\n        self.cahandler.ca_cert_chain_list = [\"cacert1\", \"cacert2\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((366, \"cert1\"), self.cahandler._cacert_expiry_get())\n        self.assertIn(\n            \"ERROR:test_a2c:CA file cacert2 does not exist\",\n            lcm.output,\n        )\n\n    def test_151__cert_expiry_get(self):\n        \"\"\"test _cert_expiry_get()\"\"\"\n        cert = Mock()\n        cert.not_valid_after = \"not_valid_after\"\n        self.assertEqual(\"not_valid_after\", self.cahandler._cert_expiry_get(cert))\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.datetime\")\n    def test_152__certexpiry_date_default(self, mock_now):\n        \"\"\"test _certexpiry_date_default()\"\"\"\n        mock_now.datetime.now.return_value = datetime.datetime(2023, 12, 31, 5, 0, 1)\n        mock_now.timedelta.return_value = datetime.timedelta(days=2)\n        self.assertEqual(\n            datetime.datetime(2024, 1, 2, 5, 0, 1),\n            self.cahandler._certexpiry_date_default(),\n        )\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cacert_expiry_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certexpiry_date_default\")\n    def test_153__certexpiry_date_set(self, mock_default, mock_get):\n        \"\"\"test _certexpiry_date_set()\"\"\"\n        mock_default.return_value = 365\n        mock_get.return_value = (720, \"cert\")\n        self.assertEqual(365, self.cahandler._certexpiry_date_set())\n        self.assertTrue(mock_default.called)\n        self.assertFalse(mock_get.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cacert_expiry_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certexpiry_date_default\")\n    def test_154__certexpiry_date_set(self, mock_default, mock_get):\n        \"\"\"test _certexpiry_date_set()\"\"\"\n        mock_default.return_value = 365\n        mock_get.return_value = (720, \"cert\")\n        self.cahandler.cert_validity_adjust = True\n        self.cahandler.cert_validity_days = 30\n        self.assertEqual(365, self.cahandler._certexpiry_date_set())\n        self.assertTrue(mock_default.called)\n        self.assertTrue(mock_get.called)\n\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._cacert_expiry_get\")\n    @patch(\"examples.ca_handler.openssl_ca_handler.CAhandler._certexpiry_date_default\")\n    def test_155__certexpiry_date_set(self, mock_default, mock_get):\n        \"\"\"test _certexpiry_date_set()\"\"\"\n        mock_default.return_value = 365\n        cert = Mock()\n        cert.not_valid_after = \"not_valid_after\"\n        mock_get.return_value = (20, cert)\n        self.cahandler.cert_validity_adjust = True\n        self.cahandler.cert_validity_days = 30\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\"not_valid_after\", self.cahandler._certexpiry_date_set())\n        self.assertIn(\n            \"INFO:test_a2c:Adjust validity to 20 days.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_default.called)\n        self.assertTrue(mock_get.called)\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_openxpki_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openxpki_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, Mock, MagicMock\nimport requests\nimport base64\nfrom OpenSSL import crypto\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n        from examples.ca_handler.openxpki_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_load\")\n    def test_002__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_load\")\n    def test_003__enter__(self, mock_cfg):\n        \"\"\"test enter api hosts defined\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.host = \"host\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_encode\")\n    def test_004_cert_bundle_create(self, mock_enc, mock_p2d):\n        \"\"\"test _cert_bundle_create()\"\"\"\n        response_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None),\n                self.cahandler._cert_bundle_create(response_dic),\n            )\n        self.assertFalse(mock_enc.called)\n        self.assertFalse(mock_p2d.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundle creation failed: malformed response from CA: {}\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_encode\")\n    def test_005_cert_bundle_create(self, mock_enc, mock_p2d):\n        \"\"\"test _cert_bundle_create()\"\"\"\n        response_dic = {\"data\": {\"certificate\": \"certificate\", \"chain\": \"chain\"}}\n        mock_enc.return_value = \"mock_enc\"\n        self.assertEqual(\n            (None, \"certificate\\nchain\", \"mock_enc\"),\n            self.cahandler._cert_bundle_create(response_dic),\n        )\n        self.assertTrue(mock_enc.called)\n        self.assertTrue(mock_p2d.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_encode\")\n    def test_006_cert_bundle_create(self, mock_enc, mock_p2d):\n        \"\"\"test _cert_bundle_create()\"\"\"\n        response_dic = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None),\n                self.cahandler._cert_bundle_create(response_dic),\n            )\n        self.assertFalse(mock_enc.called)\n        self.assertFalse(mock_p2d.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundle creation failed: malformed response from CA: {'foo': 'bar'}\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_encode\")\n    def test_007_cert_bundle_create(self, mock_enc, mock_p2d):\n        \"\"\"test _cert_bundle_create()\"\"\"\n        response_dic = {\"data\": {\"certificate\": \"certificate\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None),\n                self.cahandler._cert_bundle_create(response_dic),\n            )\n        self.assertFalse(mock_enc.called)\n        self.assertFalse(mock_p2d.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundle creation failed: malformed response from CA: {'data': {'certificate': 'certificate'}}\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.cert_pem2der\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_encode\")\n    def test_008_cert_bundle_create(self, mock_enc, mock_p2d):\n        \"\"\"test _cert_bundle_create()\"\"\"\n        response_dic = {\"data\": {\"chain\": \"chain\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None),\n                self.cahandler._cert_bundle_create(response_dic),\n            )\n        self.assertFalse(mock_enc.called)\n        self.assertFalse(mock_p2d.called)\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate bundle creation failed: malformed response from CA: {'data': {'chain': 'chain'}}\",\n            lcm.output,\n        )\n\n    def test_009__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_010__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_011__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"endpoint_name\": \"endpoint_name\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertEqual(\"endpoint_name\", self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_012__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"host\": \"host\"}\n        self.cahandler._config_server_load(parser)\n        self.assertEqual(\"host\", self.cahandler.host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_013__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"rpc_path\": \"rpc_path\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"rpc_path\", self.cahandler.rpc_path)\n\n    def test_014__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_profile_name\": \"cert_profile_name\"}\n        self.cahandler._config_ca_load(parser)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(\"cert_profile_name\", self.cahandler.cert_profile_name)\n        self.assertEqual(0, self.cahandler.polling_timeout)\n\n    def test_015__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": 10}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(10, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_016__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"20\"}\n        self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertEqual(20, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_017__config_server_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"request_timeout\": \"aaa\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_server_load(parser)\n        self.assertFalse(self.cahandler.host)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load request_timeout from config: invalid literal for int() with base 10: 'aaa'\",\n            lcm.output,\n        )\n        self.assertEqual(5, self.cahandler.request_timeout)\n        self.assertFalse(self.cahandler.endpoint_name)\n        self.assertEqual(\"/rpc/\", self.cahandler.rpc_path)\n\n    def test_018__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": False}\n        self.cahandler._config_ca_load(parser)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertEqual(0, self.cahandler.polling_timeout)\n\n    def test_019__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"\"}\n        self.cahandler._config_ca_load(parser)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertEqual(0, self.cahandler.polling_timeout)\n\n    def test_020__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_bundle\": \"ca_bundle\"}\n        self.cahandler._config_ca_load(parser)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.cert_profile_name)\n        self.assertEqual(0, self.cahandler.polling_timeout)\n\n    def test_021__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"polling_timeout\": 10}\n        self.cahandler._config_ca_load(parser)\n        self.assertEqual(10, self.cahandler.polling_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.cert_profile_name)\n\n    def test_022__config_ca_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"polling_timeout\": \"polling_timeout\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_ca_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load polling_timeout from config: invalid literal for int() with base 10: 'polling_timeout'\",\n            lcm.output,\n        )\n        self.assertEqual(0, self.cahandler.polling_timeout)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.cert_profile_name)\n\n    def test_023__config_session_load(self):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertFalse(self.cahandler.client_cert)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: missing \"client_cert\", \"client_key\", or \"client_passphrase variable\" in config file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_passphrase_load\")\n    def test_024__config_session_load(self, mock_pass):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"client_cert\": \"client_cert\", \"client_key\": \"client_key\"}\n        self.cahandler._config_session_load(parser)\n        self.assertEqual((\"client_cert\", \"client_key\"), self.cahandler.session.cert)\n        self.assertFalse(mock_pass.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_passphrase_load\")\n    def test_025__config_session_load(self, mock_pass):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"client_cert\": \"client_cert\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_session_load(parser)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: missing \"client_cert\", \"client_key\", or \"client_passphrase variable\" in config file.',\n            lcm.output,\n        )\n        self.assertTrue(mock_pass.called)\n\n    @patch(\"requests.Session\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.Pkcs12Adapter\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_passphrase_load\")\n    def test_026__config_session_load(self, mock_pass, mock_req, mock_session):\n        \"\"\"test _config_server_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"client_cert\": \"client_cert\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        mock_session.return_value.__enter__.return_value = Mock()\n        self.cahandler.cert_passphrase = \"cert_passphrase\"\n        self.cahandler._config_session_load(parser)\n        self.assertTrue(mock_pass.called)\n        self.assertTrue(mock_req.called)\n\n    def test_027__config_passphrase_load(self):\n        \"\"\"test _config_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        self.cahandler._config_passphrase_load(parser)\n        self.assertFalse(self.cahandler.cert_passphrase)\n\n    def test_028__config_passphrase_load(self):\n        \"\"\"test _config_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase\": \"cert_passphrase\"}\n        self.cahandler._config_passphrase_load(parser)\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_variable\": \"cert_passphrase_variable\"})\n    def test_029__config_passphrase_load(self):\n        \"\"\"test _config_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_variable\"}\n        self.cahandler._config_passphrase_load(parser)\n        self.assertEqual(\"cert_passphrase_variable\", self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"bar\"})\n    def test_030__config_passphrase_load(self):\n        \"\"\"test _config_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"cert_passphrase_variable\": \"cert_passphrase_variable\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_passphrase_load(parser)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load cert_passphrase_variable from environment: 'cert_passphrase_variable'\",\n            lcm.output,\n        )\n        self.assertFalse(self.cahandler.cert_passphrase)\n\n    @patch.dict(\"os.environ\", {\"cert_passphrase_variable\": \"cert_passphrase_variable\"})\n    def test_031__config_passphrase_load(self):\n        \"\"\"test _config_passphrase_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_passphrase_variable\": \"cert_passphrase_variable\",\n            \"cert_passphrase\": \"cert_passphrase\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_passphrase_load(parser)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite cert_passphrase\",\n            lcm.output,\n        )\n        self.assertEqual(\"cert_passphrase\", self.cahandler.cert_passphrase)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.load_config\")\n    def test_032_config_load(self, mock_load_cfg, mock_auth_load, mock_server_load):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"endpoint_name\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.load_config\")\n    def test_033_config_load(self, mock_load_cfg):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"client_cert\": \"client_cert\", \"ca_bundle\": False}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Client authentication requires ca_bundle to be enabled in configuration.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.load_config\")\n    def test_034_config_load(self, mock_load_cfg, mock_auth_load, mock_server_load):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.host = \"host\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"endpoint_name\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.load_config\")\n    def test_035_config_load(self, mock_load_cfg, mock_auth_load, mock_server_load):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.cert_profile_name = \"cert_profile_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"host\" is missing in configuration file.',\n            lcm.output,\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"endpoint_name\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_server_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._config_session_load\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.load_config\")\n    def test_036_config_load(self, mock_load_cfg, mock_auth_load, mock_server_load):\n        \"\"\"load config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertTrue(mock_auth_load.called)\n        self.assertTrue(mock_server_load.called)\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"host\" is missing in configuration file.',\n            lcm.output,\n        )\n        self.assertIn(\n            'ERROR:test_a2c:Configuration incomplete: parameter \"cert_profile_name\" is missing in configuration file.',\n            lcm.output,\n        )\n\n    def test_037_poll(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_038_trigger(self):\n        \"\"\"test polling\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    @patch.object(requests, \"post\")\n    def test_039__rpc_post(self, mock_req):\n        \"\"\"test _api_post successful run\"\"\"\n        mockresponse2 = Mock()\n        mockresponse2.json = lambda: {\"foo\": \"bar\"}\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [mockresponse2]\n        self.cahandler.session = mockresponse\n        self.cahandler.host = \"host\"\n        self.assertEqual({\"foo\": \"bar\"}, self.cahandler._rpc_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_040__rpc_post(self, mock_post):\n        \"\"\"CAhandler.get_ca() returns an http error\"\"\"\n        self.cahandler.host = \"api_host\"\n        mockresponse = Mock()\n        mockresponse.post.side_effect = [Exception(\"exc_api_post\")]\n        self.cahandler.session = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._rpc_post(\"url\", \"data\"))\n        self.assertIn(\n            \"ERROR:test_a2c:RPC POST request failed: exc_api_post\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_url_recode\")\n    def test_041_enroll(self, mock_recode, mock_pem, mock_enroll):\n        \"\"\"test ernoll\"\"\"\n        csr = \"csr\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Configuration incomplete\", None, None, None),\n                self.cahandler.enroll(csr),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete: host variable is missing.\",\n            lcm.output,\n        )\n        self.assertFalse(mock_recode.called)\n        self.assertFalse(mock_pem.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_url_recode\")\n    def test_042_enroll(self, mock_recode, mock_pem, mock_enroll):\n        \"\"\"test ernoll\"\"\"\n        csr = \"csr\"\n        self.cahandler.host = \"host\"\n        mock_recode.return_value = \"mock_recode\"\n        mock_pem.return_value = \"mock_pem\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Configuration incomplete\", None, None, None),\n                self.cahandler.enroll(csr),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration incomplete: client authentication is missing.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_pem.called)\n        self.assertFalse(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._enroll\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.b64_url_recode\")\n    def test_043_enroll(self, mock_recode, mock_pem, mock_enroll):\n        \"\"\"test ernoll\"\"\"\n        csr = \"csr\"\n        self.cahandler.host = \"host\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.session = \"session\"\n        mock_enroll.return_value = (\n            \"error\",\n            \"cert_bundle\",\n            \"cert_raw\",\n            \"poll_indentifier\",\n        )\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"poll_indentifier\"),\n            self.cahandler.enroll(csr),\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_pem.called)\n        self.assertTrue(mock_enroll.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_044__enroll(self, mock_post, mock_create):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.return_value = {\"foo\": \"bar\"}\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Malformed response\", None, None, None),\n                self.cahandler._enroll({\"foo\": \"bar\"}),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Malformed response from CA during enrollment: {'foo': 'bar'}\",\n            lcm.output,\n        )\n        self.assertFalse(mock_create.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_045__enroll(self, mock_post, mock_create):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.return_value = {\n            \"result\": {\n                \"id\": \"id\",\n                \"state\": \"pending\",\n                \"data\": {\"transaction_id\": \"transaction_id\"},\n            }\n        }\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None, \"transaction_id\"),\n                self.cahandler._enroll({\"foo\": \"bar\"}),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Request pending. Transaction_id: transaction_id Workflow_id: id\",\n            lcm.output,\n        )\n        self.assertFalse(mock_create.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_046__enroll(self, mock_post, mock_create, mock_log):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.return_value = {\n            \"result\": {\n                \"id\": \"id\",\n                \"state\": \"SUCCESS\",\n                \"data\": {\"cert_identifier\": \"cert_identifier\"},\n            }\n        }\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        mock_create.return_value = (\"error\", \"cert_bundle\", \"cert_raw\")\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"cert_identifier\"),\n            self.cahandler._enroll({\"foo\": \"bar\"}),\n        )\n        self.assertTrue(mock_create.called)\n        self.assertFalse(mock_log.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_047__enroll(self, mock_post, mock_create):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.side_effect = [\n            {\n                \"result\": {\n                    \"id\": \"id\",\n                    \"state\": \"pending\",\n                    \"data\": {\"transaction_id\": \"transaction_id\"},\n                }\n            },\n            {\n                \"result\": {\n                    \"id\": \"id\",\n                    \"state\": \"SUCCESS\",\n                    \"data\": {\"transaction_id\": \"transaction_id\"},\n                }\n            },\n        ]\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None, None, \"transaction_id\"),\n                self.cahandler._enroll({\"foo\": \"bar\"}),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Request pending. Transaction_id: transaction_id Workflow_id: id\",\n            lcm.output,\n        )\n        self.assertFalse(mock_create.called)\n\n    @patch(\"time.sleep\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_048__enroll(self, mock_post, mock_create, mock_sleep):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.side_effect = [\n            {\n                \"result\": {\n                    \"id\": \"id\",\n                    \"state\": \"pending\",\n                    \"data\": {\"transaction_id\": \"transaction_id\"},\n                }\n            },\n            {\n                \"result\": {\n                    \"id\": \"id\",\n                    \"state\": \"SUCCESS\",\n                    \"data\": {\"cert_identifier\": \"cert_identifier\"},\n                }\n            },\n        ]\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.polling_timeout = 60\n        mock_sleep.return_value = Mock()\n        mock_create.return_value = (\"error\", \"cert_bundle\", \"cert_raw\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"error\", \"cert_bundle\", \"cert_raw\", \"cert_identifier\"),\n                self.cahandler._enroll({\"foo\": \"bar\"}),\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Request pending. Transaction_id: transaction_id Workflow_id: id\",\n            lcm.output,\n        )\n        self.assertTrue(mock_create.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_bundle_create\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_049__enroll(self, mock_post, mock_create, mock_log):\n        \"\"\"test _enroll()\"\"\"\n        mock_post.return_value = {\n            \"result\": {\n                \"id\": \"id\",\n                \"state\": \"SUCCESS\",\n                \"data\": {\"cert_identifier\": \"cert_identifier\"},\n            }\n        }\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.enrollment_config_log = True\n        mock_create.return_value = (\"error\", \"cert_bundle\", \"cert_raw\")\n        self.assertEqual(\n            (\"error\", \"cert_bundle\", \"cert_raw\", \"cert_identifier\"),\n            self.cahandler._enroll({\"foo\": \"bar\"}),\n        )\n        self.assertTrue(mock_create.called)\n        self.assertTrue(mock_log.called)\n\n    def test_050__cert_identifier_get(self):\n        \"\"\"test _cert_identifier_get()\"\"\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.dbstore.certificate_lookup.return_value = {\n            \"poll_identifier\": \"cert_identifier\"\n        }\n        self.assertEqual(\n            \"cert_identifier\", self.cahandler._cert_identifier_get(\"certcn\")\n        )\n\n    def test_051__cert_identifier_get(self):\n        \"\"\"test _cert_identifier_get()\"\"\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.dbstore.certificate_lookup.return_value = {\"poll_identifier\": \"\"}\n        self.assertFalse(self.cahandler._cert_identifier_get(\"certcn\"))\n\n    def test_052__cert_identifier_get(self):\n        \"\"\"test _cert_identifier_get()\"\"\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.dbstore.certificate_lookup.return_value = {\n            \"poll_identifier\": None\n        }\n        self.assertFalse(self.cahandler._cert_identifier_get(\"certcn\"))\n\n    def test_053__cert_identifier_get(self):\n        \"\"\"test _cert_identifier_get()\"\"\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.dbstore.certificate_lookup.return_value = {\"foo\": \"bar\"}\n        self.assertFalse(self.cahandler._cert_identifier_get(\"certcn\"))\n\n    def test_054__cert_identifier_get(self):\n        \"\"\"test _cert_identifier_get()\"\"\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        self.cahandler.dbstore.certificate_lookup.return_value = {\"poll_identifier\": \"\"}\n        self.assertFalse(self.cahandler._cert_identifier_get(\"certcn\"))\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_055__revoke(self, mock_post):\n        \"\"\"test _revoke()\"\"\"\n        self.assertEqual(\n            (\n                400,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Incomplete configuration\",\n            ),\n            self.cahandler._revoke(\"cert_identifier\", \"rev_reason\"),\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_056__revoke(self, mock_post):\n        \"\"\"test _revoke()\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        mock_post.return_value = {\"result\": {\"state\": \"failure\"}}\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"Revocation failed\"),\n            self.cahandler._revoke(\"cert_identifier\", \"rev_reason\"),\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._rpc_post\")\n    def test_057__revoke(self, mock_post):\n        \"\"\"test _revoke()\"\"\"\n        self.cahandler.host = \"host\"\n        self.cahandler.endpoint_name = \"endpoint_name\"\n        mock_post.return_value = {\"result\": {\"state\": \"success\"}}\n        self.assertEqual(\n            (200, None, None), self.cahandler._revoke(\"cert_identifier\", \"rev_reason\")\n        )\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._revoke\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_identifier_get\")\n    def test_058_revoke(self, mock_certid, mock_revoke):\n        \"\"\"test revoke\"\"\"\n        mock_certid.return_value = None\n        self.assertEqual(\n            (400, \"urn:ietf:params:acme:error:serverInternal\", \"Unknown status\"),\n            self.cahandler.revoke(\"cert\", \"reason\", \"date\"),\n        )\n        self.assertTrue(mock_certid.called)\n        self.assertFalse(mock_revoke.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._revoke\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_identifier_get\")\n    def test_059_revoke(self, mock_certid, mock_revoke, mock_eab):\n        \"\"\"test revoke\"\"\"\n        mock_certid.return_value = \"cert_identifier\"\n        mock_revoke.return_value = (\"code\", \"error\", \"detail\")\n        self.assertEqual(\n            (\"code\", \"error\", \"detail\"), self.cahandler.revoke(\"cert\", \"reason\", \"date\")\n        )\n        self.assertTrue(mock_certid.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._revoke\")\n    @patch(\"examples.ca_handler.openxpki_ca_handler.CAhandler._cert_identifier_get\")\n    def test_060_revoke(self, mock_certid, mock_revoke, mock_eab):\n        \"\"\"test revoke\"\"\"\n        mock_certid.return_value = \"cert_identifier\"\n        mock_revoke.return_value = (\"code\", \"error\", \"detail\")\n        self.cahandler.eab_profiling = True\n        self.assertEqual(\n            (\"code\", \"error\", \"detail\"), self.cahandler.revoke(\"cert\", \"reason\", \"date\")\n        )\n        self.assertTrue(mock_certid.called)\n        self.assertTrue(mock_revoke.called)\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.openxpki_ca_handler.handler_config_check\")\n    def test_061_handler_check(self, mock_handler_check):\n        \"\"\"test handler_check\"\"\"\n        mock_handler_check.return_value = \"mock_handler_check\"\n        self.assertEqual(\"mock_handler_check\", self.cahandler.handler_check())\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_order.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Comprehensive unittests for order.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nfrom unittest.mock import patch, MagicMock, call, ANY\n\nimport logging\nimport types\nimport json\nimport os\nimport sys\n\n# Inject a mock acme_srv.db_handler.DBstore into sys.modules if missing\nimport types as _types\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestOrderRepository(unittest.TestCase):\n    def setUp(self):\n\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.dbstore = MagicMock()\n        from acme_srv.order import OrderRepository\n\n        self.order_repository = OrderRepository(self.dbstore, self.logger)\n\n    def test_001_add_order_success(self):\n        self.order_repository.dbstore.order_add.return_value = \"oid\"\n        self.assertEqual(self.order_repository.add_order({\"foo\": \"bar\"}), \"oid\")\n\n    def test_002_add_order_failure(self):\n        self.order_repository.dbstore.order_add.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.add_order({\"foo\": \"bar\"})\n        self.assertIn(\n            \"Failed to add order: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to add order: fail\",\n            log_cm.output,\n        )\n\n    def test_003_add_authorization_success(self):\n        self.order_repository.dbstore.authorization_add.return_value = \"aid\"\n        self.assertEqual(self.order_repository.add_authorization({\"foo\": \"bar\"}), \"aid\")\n\n    def test_004_add_authorization_failure(self):\n        self.order_repository.dbstore.authorization_add.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.add_authorization({\"foo\": \"bar\"})\n        self.assertIn(\n            \"Failed to add authorization: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to add authorization: fail\",\n            log_cm.output,\n        )\n\n    def test_005_update_authorization_success(self):\n        self.order_repository.dbstore.authorization_update.return_value = \"ok\"\n        self.assertEqual(\n            self.order_repository.update_authorization({\"foo\": \"bar\"}), \"ok\"\n        )\n\n    def test_006_update_authorization_failure(self):\n        self.order_repository.dbstore.authorization_update.side_effect = Exception(\n            \"fail\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.update_authorization({\"foo\": \"bar\"})\n        self.assertIn(\n            \"Failed to update authorization: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to update authorization: fail\",\n            log_cm.output,\n        )\n\n    def test_007_order_lookup_success(self):\n        self.order_repository.dbstore.order_lookup.return_value = {\"name\": \"order1\"}\n        self.assertEqual(\n            self.order_repository.order_lookup(\"name\", \"order1\"), {\"name\": \"order1\"}\n        )\n\n    def test_008_order_lookup_failure(self):\n        self.order_repository.dbstore.order_lookup.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.order_lookup(\"name\", \"order1\")\n        self.assertIn(\n            \"Failed to look up order: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up order: fail\",\n            log_cm.output,\n        )\n\n    def test_009_order_update_success(self):\n        self.order_repository.dbstore.order_update.return_value = \"ok\"\n        self.assertEqual(self.order_repository.order_update({\"foo\": \"bar\"}), \"ok\")\n\n    def test_010_order_update_failure(self):\n        self.order_repository.dbstore.order_update.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.order_update({\"foo\": \"bar\"})\n        self.assertIn(\n            \"Failed to update order: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to update order: fail\",\n            log_cm.output,\n        )\n\n    def test_011_authorization_lookup_success(self):\n        self.order_repository.dbstore.authorization_lookup.return_value = [\n            {\"name\": \"auth1\"}\n        ]\n        self.assertEqual(\n            self.order_repository.authorization_lookup(\"key\", \"val\", [\"name\"]),\n            [{\"name\": \"auth1\"}],\n        )\n\n    def test_012_authorization_lookup_failure(self):\n        self.order_repository.dbstore.authorization_lookup.side_effect = Exception(\n            \"fail\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.authorization_lookup(\"key\", \"val\", [\"name\"])\n        self.assertIn(\n            \"Failed to look up authorization: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up authorization: fail\",\n            log_cm.output,\n        )\n\n    def test_013_certificate_lookup_success(self):\n        self.order_repository.dbstore.certificate_lookup.return_value = {\n            \"name\": \"cert1\"\n        }\n        self.assertEqual(\n            self.order_repository.certificate_lookup(\"key\", \"val\"), {\"name\": \"cert1\"}\n        )\n\n    def test_014_certificate_lookup_failure(self):\n        self.order_repository.dbstore.certificate_lookup.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.certificate_lookup(\"key\", \"val\")\n        self.assertIn(\n            \"Failed to look up certificate: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up certificate: fail\",\n            log_cm.output,\n        )\n\n    def test_015_hkparameter_get_success(self):\n        self.order_repository.dbstore.hkparameter_get.return_value = \"profiles\"\n        self.assertEqual(self.order_repository.hkparameter_get(\"profiles\"), \"profiles\")\n\n    def test_016_hkparameter_get_failure(self):\n        self.order_repository.dbstore.hkparameter_get.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.hkparameter_get(\"profiles\")\n        self.assertIn(\n            \"Failed to get hkparameter: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to get hkparameter: fail\",\n            log_cm.output,\n        )\n\n    def test_017_orders_invalid_search_success(self):\n        self.order_repository.dbstore.orders_invalid_search.return_value = [\"order1\"]\n        self.assertEqual(\n            self.order_repository.orders_invalid_search(\"expires\", 0, [], \"<=\"),\n            [\"order1\"],\n        )\n\n    def test_018_orders_invalid_search_failure(self):\n        self.order_repository.dbstore.orders_invalid_search.side_effect = Exception(\n            \"fail\"\n        )\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.orders_invalid_search(\"expires\", 0, [], \"<=\")\n        self.assertIn(\n            \"Failed to search for invalid orders: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to search for invalid orders: fail\",\n            log_cm.output,\n        )\n\n    def test_019_account_lookup_success(self):\n        self.order_repository.dbstore.account_lookup.return_value = {\"name\": \"acct1\"}\n        self.assertEqual(\n            self.order_repository.account_lookup(\"name\", \"acct1\"), {\"name\": \"acct1\"}\n        )\n\n    def test_020_account_lookup_failure(self):\n        self.order_repository.dbstore.account_lookup.side_effect = Exception(\"fail\")\n        with self.assertRaises(Exception) as context:\n            with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n                self.order_repository.account_lookup(\"name\", \"acct1\")\n        self.assertIn(\n            \"Failed to look up account: fail\",\n            str(context.exception),\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up account: fail\",\n            log_cm.output,\n        )\n\n\nclass TestOrderClass(unittest.TestCase):\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.order import Message\n        from acme_srv.order import Order\n        from acme_srv.signature import Signature\n\n        self.message = Message(False, \"http://tester.local\", self.logger)\n        self.signature = Signature(False, \"http://tester.local\", self.logger)\n        self.order = Order(debug=True, server_name=\"test\", logger=self.logger)\n        self.order.repository = MagicMock()\n\n    def test_021__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.order.__enter__()\n\n    def test_022__enter_(self):\n        \"\"\"test enter\"\"\"\n        self.order.__exit__()\n\n    def test_023_are_identifiers_allowed_logging(self):\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.order.are_identifiers_allowed([{\"type\": \"foo\", \"value\": \"bar\"}])\n        self.assertIn(\"DEBUG:test_a2c:Order.are_identifiers_allowed()\", log_cm.output)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order.are_identifiers_allowed() ended with: urn:ietf:params:acme:error:unsupportedIdentifier\",\n            log_cm.output,\n        )\n\n    def test_024_is_profile_valid_profile_check_disabled(self):\n        self.order.config.profiles_check_disable = False\n        self.order.config.profiles = {\"foo\": {}}\n        result = self.order.is_profile_valid(\"foo\")\n        self.assertIsNone(result)\n\n    def test_025_is_profile_valid_invalid(self):\n        self.order.config.profiles_check_disable = False\n        self.order.config.profiles = {\"bar\": {}}\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n            self.assertEqual(\n                self.order.is_profile_valid(\"foo\"),\n                \"urn:ietf:params:acme:error:invalidProfile\",\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Profile 'foo' is not valid. Ignoring submitted profile.\",\n            log_cm.output,\n        )\n\n    def test_026_is_profile_valid_valid_profile(self):\n\n        self.order.config.profiles_check_disable = True\n        self.order.config.profiles = {\"foo\": {}}\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.assertIsNone(self.order.is_profile_valid(\"foo\"))\n\n        self.assertIn(\"DEBUG:test_a2c:Order.is_profile_valid(foo)\", log_cm.output)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order.is_profile_valid() ended with None\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order.is_profile_valid(): profile check disabled\",\n            log_cm.output,\n        )\n        self.assertNotIn(\n            \"WARNING:Profile 'foo' is not valid. Ignoring submitted profile.\",\n            log_cm.output,\n        )\n\n    def test_027_add_profile_to_order_valid(self):\n        self.order.config.profiles = {\"foo\": {}}\n        self.order.config.profiles_check_disable = False\n        data_dic = {}\n        payload = {\"profile\": \"foo\"}\n        error, updated_dic = self.order.add_profile_to_order(data_dic, payload)\n        self.assertIsNone(error)\n        self.assertEqual(updated_dic[\"profile\"], \"foo\")\n\n    def test_028_add_profile_to_order_invalid(self):\n        self.order.config.profiles = {}\n        self.order.config.profiles_check_disable = False\n        data_dic = {}\n        payload = {\"profile\": \"foo\"}\n        error, updated_dic = self.order.add_profile_to_order(data_dic, payload)\n        self.assertEqual(error, \"urn:ietf:params:acme:error:invalidProfile\")\n        self.assertNotIn(\"profile\", updated_dic)\n\n    def test_029_add_profile_to_order_no_profiles_configured(self):\n        self.order.config.profiles = {}\n        self.order.config.profiles_check_disable = False\n        data_dic = {}\n        payload = {\"profile\": \"foo\"}\n        # Patch is_profile_valid to return None (simulate valid profile)\n        with patch.object(self.order, \"is_profile_valid\", return_value=None):\n            with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n                error, updated_dic = self.order.add_profile_to_order(data_dic, payload)\n                self.assertIsNone(error)\n                self.assertNotIn(\"profile\", updated_dic)\n            self.assertIn(\n                \"WARNING:test_a2c:Ignore submitted profile 'foo' as no profiles are configured.\",\n                log_cm.output,\n            )\n\n    def test_030_process_order_request_db_error_logging(self):\n        self.order.repository.certificate_lookup.side_effect = Exception(\"DB error\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            result = self.order._process_order_request(\n                \"order1\", {\"url\": \"poll\"}, {}, None\n            )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: Certificate lookup failed: DB error\",\n            log_cm.output,\n        )\n\n    def test_031_edge_case_empty_identifiers(self):\n        # _check_identifiers_validity with empty list\n        result = self.order._check_identifiers_validity(\n            []\n        )  # Edge case with empty identifiers\n        self.assertEqual(\n            result,\n            (self.order.error_msg_dic[\"malformed\"], \"malformed identifiers list\"),\n        )\n\n    def test_032_edge_case_too_many_identifiers(self):\n        # _check_identifiers_validity with too many identifiers\n        self.order.config.identifier_limit = 1  # Set identifier limit to 1 for testing\n        result = self.order._check_identifiers_validity(\n            [{\"type\": \"dns\", \"value\": \"a\"}, {\"type\": \"dns\", \"value\": \"b\"}]\n        )\n        self.assertEqual(\n            result,\n            (\n                self.order.error_msg_dic[\"rejectedidentifier\"],\n                \"identifier limit exceeded\",\n            ),\n        )\n\n    def test_033_edge_case_invalid_identifier_type(self):\n        # are_identifiers_allowed with unsupported type\n        self.order.config.tnauthlist_support = False\n        self.order.config.email_identifier_support = False\n        result = self.order.are_identifiers_allowed([{\"type\": \"foo\", \"value\": \"bar\"}])\n        self.assertEqual(\n            result,\n            (\n                self.order.error_msg_dic[\"unsupportedidentifier\"],\n                \"Identifier type foo not supported\",\n            ),\n        )\n\n    def test_034_edge_case_missing_type(self):\n        # are_identifiers_allowed with missing type\n        result = self.order.are_identifiers_allowed([{\"value\": \"bar\"}])\n        self.assertEqual(\n            result,\n            (self.order.error_msg_dic[\"malformed\"], \"Identifier type is missing\"),\n        )\n\n    def test_035_edge_case_invalid_profile_config(self):\n        # _set_profiles_from_db with invalid JSON\n        with patch.object(self.order.logger, \"error\") as mock_log:\n            self.order._set_profiles_from_db(\"notjson\")\n            mock_log.assert_called()\n\n    def test_036_order_dic_create_all_fields(self):\n        # test _order_dic_create with all fields\n        tmp_dic = {\n            \"status\": \"pending\",\n            \"expires\": 1234567890,\n            \"notbefore\": 1234567891,\n            \"notafter\": 1234567892,\n            \"identifiers\": json.dumps([{\"type\": \"dns\", \"value\": \"a\"}]),\n        }\n        result = self.order._order_dic_create(tmp_dic)\n        self.assertEqual(result[\"status\"], \"pending\")\n        self.assertEqual(result[\"expires\"], \"2009-02-13T23:31:30Z\")\n        self.assertEqual(result[\"notBefore\"], \"2009-02-13T23:31:31Z\")\n        self.assertEqual(result[\"notAfter\"], \"2009-02-13T23:31:32Z\")\n        self.assertIsInstance(result[\"identifiers\"], list)\n\n    def test_037_order_dic_create_invalid_identifiers(self):\n        # test _order_dic_create with invalid JSON in identifiers\n        tmp_dic = {\"identifiers\": \"notjson\"}\n        with patch.object(self.order.logger, \"error\") as mock_log:\n            result = self.order._order_dic_create(tmp_dic)\n            self.assertIsNone(result.get(\"identifiers\"))\n            self.assertIn(\"identifiers\", tmp_dic)\n            mock_log.assert_called()\n\n    def test_038_get_authorization_list_db_error(self):\n        # test _get_authorization_list with DB error\n        self.order.repository.authorization_lookup.side_effect = Exception(\"DB error\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            self.order._get_authorization_list(\"order\")\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up authorization list: DB error\",\n            log_cm.output,\n        )\n\n    def test_039_update_validity_list_ready(self):\n        # test _update_validity_list sets order to ready\n        authz_list = [{\"name\": \"auth1\", \"status__name\": \"valid\"}]\n        order_dic = {\"status\": \"pending\", \"authorizations\": []}\n        with patch.object(self.order.repository, \"order_update\") as mock_update:\n            self.order._update_validity_list(authz_list, order_dic, \"_\")\n            mock_update.assert_called_with({\"name\": \"_\", \"status\": \"ready\"})\n\n    def test_040_update_validity_list_not_ready(self):\n        # test _update_validity_list does not set order to ready\n        authz_list = [{\"name\": \"auth1\", \"status__name\": \"pending\"}]\n        order_dic = {\"status\": \"pending\", \"authorizations\": []}\n        with patch.object(self.order.repository, \"order_update\") as mock_update:\n            self.order._update_validity_list(authz_list, order_dic, \"_\")\n            mock_update.assert_not_called()\n\n    def test_041_get_order_details_with_authz(self):\n        # test get_order_details with authorizations\n        self.order.repository.order_lookup.return_value = {\n            \"status\": \"pending\",\n            \"identifiers\": json.dumps([{\"type\": \"dns\", \"value\": \"a\"}]),\n        }\n        self.order.repository.authorization_lookup.return_value = [\n            {\"name\": \"auth1\", \"status__name\": \"valid\"}\n        ]\n        with patch.object(self.order, \"_update_validity_list\") as mock_update:\n            result = self.order.get_order_details(\"order1\")\n            mock_update.assert_called()\n            self.assertIn(\"status\", result)\n\n    def test_042_invalidate_expired_orders(self):\n        # test invalidate_expired_orders with valid and invalid orders\n        self.order.repository.orders_invalid_search.return_value = [\n            {\"name\": \"order1\", \"status__name\": \"pending\"},\n            {\"name\": \"order2\", \"status__name\": \"invalid\"},\n        ]\n        with patch.object(self.order.repository, \"order_update\") as mock_update:\n            _, output = self.order.invalidate_expired_orders(1234567890)\n            self.assertIn(\"order1\", [o[\"name\"] for o in output])\n            self.assertNotIn(\"order2\", [o[\"name\"] for o in output])\n            mock_update.assert_called_once_with({\"name\": \"order1\", \"status\": \"invalid\"})\n\n    def test_043_create_from_content_success(self):\n        # test create_from_content with successful order creation\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(\n                200,\n                None,\n                None,\n                None,\n                {\"identifiers\": [{\"type\": \"dns\", \"value\": \"a\"}]},\n                \"account\",\n            ),\n        ):\n            with patch.object(\n                self.order,\n                \"create_order\",\n                return_value=(\n                    None,\n                    \"detail\",\n                    \"order\",\n                    {\"auth1\": {\"type\": \"dns\", \"value\": \"a\"}},\n                    \"2026-01-01T00:00:00Z\",\n                ),\n            ):\n                with patch.object(\n                    self.order.message,\n                    \"prepare_response\",\n                    side_effect=lambda resp, stat: resp,\n                ):\n                    result = self.order.create_from_content(\"content\")\n                    self.assertIn(\"header\", result)\n                    self.assertIn(\"data\", result)\n\n    def test_044_create_from_content_rejected(self):\n        # test create_from_content with rejected identifier\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(\n                200,\n                None,\n                None,\n                None,\n                {\"identifiers\": [{\"type\": \"dns\", \"value\": \"a\"}]},\n                \"account\",\n            ),\n        ):\n            with patch.object(\n                self.order,\n                \"create_order\",\n                return_value=(\n                    \"rejectedidentifier\",\n                    \"detail\",\n                    \"order\",\n                    {},\n                    \"2026-01-01T00:00:00Z\",\n                ),\n            ):\n                with patch.object(\n                    self.order.message,\n                    \"prepare_response\",\n                    side_effect=lambda resp, stat: resp,\n                ):\n                    result = self.order.create_from_content(\"content\")\n                    self.assertTrue(\n                        \"data\" not in result or result.get(\"data\", {}) == {}\n                    )\n\n    def test_045_create_from_content_error(self):\n        # test create_from_content with generic error\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(\n                200,\n                None,\n                None,\n                None,\n                {\"identifiers\": [{\"type\": \"dns\", \"value\": \"a\"}]},\n                \"account\",\n            ),\n        ):\n            with patch.object(\n                self.order,\n                \"create_order\",\n                return_value=(\n                    \"someerror\",\n                    \"detail\",\n                    \"order\",\n                    {},\n                    \"2026-01-01T00:00:00Z\",\n                ),\n            ):\n                with patch.object(\n                    self.order.message,\n                    \"prepare_response\",\n                    side_effect=lambda resp, stat: resp,\n                ):\n                    result = self.order.create_from_content(\"content\")\n                    self.assertTrue(\n                        \"data\" not in result or result.get(\"data\", {}) == {}\n                    )\n\n    def test_046_create_from_content_check_fail(self):\n        # test create_from_content with check returning error\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(400, \"err\", \"detail\", None, None, None),\n        ):\n            with patch.object(\n                self.order.message,\n                \"prepare_response\",\n                side_effect=lambda resp, stat: resp,\n            ):\n                result = self.order.create_from_content(\"content\")\n                self.assertIsInstance(result, dict)\n\n    def test_047_parse_order_message_all_paths(self):\n        # test _parse_order_message for all code paths\n        # url in protected, order_name, order_dic, process_order_request\n        with patch.object(self.order, \"_name_get\", return_value=\"order\"):\n            with patch.object(\n                self.order, \"get_order_details\", return_value={\"status\": \"ok\"}\n            ):\n                with patch.object(\n                    self.order,\n                    \"_process_order_request\",\n                    return_value=(\n                        200,\n                        \"msg\",\n                        \"detail\",\n                        \"cert\",\n                    ),\n                ):\n                    (\n                        code,\n                        _msg,\n                        _detail,\n                        _cert,\n                        _order,\n                    ) = self.order._parse_order_message({\"url\": \"url\"}, {}, None)\n                    self.assertEqual(code, 200)\n            # url in protected, order_name, no order_dic\n            with patch.object(self.order, \"get_order_details\", return_value={}):\n                code, _msg, _detail, _cert, _order = self.order._parse_order_message(\n                    {\"url\": \"url\"}, {}, None\n                )\n                self.assertEqual(code, 403)\n            # url in protected, no order_name\n            with patch.object(self.order, \"_name_get\", return_value=None):\n                code, _msg, _detail, _cert, _order = self.order._parse_order_message(\n                    {\"url\": \"url\"}, {}, None\n                )\n                self.assertEqual(code, 400)\n        # no url in protected\n        code, _msg, _detail, _cert, _order = self.order._parse_order_message(\n            {}, {}, None\n        )\n        self.assertEqual(code, 400)\n\n    def test_048_parse_order_content_success(self):\n        # test parse_order_content with code 200 and status processing\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(200, None, None, {\"url\": \"url\"}, {}, \"account\"),\n        ):\n            with patch.object(\n                self.order,\n                \"_parse_order_message\",\n                return_value=(200, None, None, None, \"order\"),\n            ):\n                with patch.object(\n                    self.order,\n                    \"get_order_details\",\n                    return_value={\"status\": \"processing\"},\n                ):\n                    with patch.object(\n                        self.order.message,\n                        \"prepare_response\",\n                        side_effect=lambda resp, stat: resp,\n                    ):\n                        result = self.order.parse_order_content(\"content\")\n                        self.assertIn(\"header\", result)\n                        self.assertIn(\"data\", result)\n\n    def test_049_parse_order_content_expiry_disabled(self):\n        # test parse_order_content with expiry_check_disable True\n        self.order.config.expiry_check_disable = True\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(200, None, None, {\"url\": \"url\"}, {}, \"account\"),\n        ):\n            with patch.object(\n                self.order,\n                \"_parse_order_message\",\n                return_value=(200, None, None, None, \"order\"),\n            ):\n                with patch.object(\n                    self.order,\n                    \"get_order_details\",\n                    return_value={\"status\": \"processing\"},\n                ):\n                    with patch.object(\n                        self.order.message,\n                        \"prepare_response\",\n                        side_effect=lambda resp, stat: resp,\n                    ):\n                        result = self.order.parse_order_content(\"content\")\n                        self.assertIn(\"header\", result)\n                        self.assertIn(\"data\", result)\n\n    def test_050_legacy_api_compatibility(self):\n        # test legacy API wrappers\n        with patch.object(\n            self.order, \"invalidate_expired_orders\", return_value=([], [])\n        ):\n            self.assertEqual(self.order.invalidate(), ([], []))\n        with patch.object(\n            self.order, \"create_from_content\", return_value={\"foo\": \"bar\"}\n        ):\n            self.assertEqual(self.order.new(\"content\"), {\"foo\": \"bar\"})\n        with patch.object(\n            self.order, \"parse_order_content\", return_value={\"foo\": \"bar\"}\n        ):\n            self.assertEqual(self.order.parse(\"content\"), {\"foo\": \"bar\"})\n\n    def test_051_add_order_and_authorizations_success(self):\n        # Order and authorizations added successfully\n        self.order.repository.add_order.return_value = \"oid\"\n        self.order.repository.add_authorization.return_value = None\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        data_dic = {\"foo\": \"bar\"}\n        auth_dic = {}\n        error = None\n        with patch.object(\n            self.order,\n            \"_add_authorizations_to_db\",\n            wraps=self.order._add_authorizations_to_db,\n        ) as mock_add_authz:\n\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                result = self.order._add_order_and_authorizations(\n                    data_dic, auth_dic, payload, error\n                )\n                self.assertIsNone(result)\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations()\", log_cm.output\n            )\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations() ended with None\",\n                log_cm.output,\n            )\n            mock_add_authz.assert_called_once()\n\n    def test_052_add_order_and_authorizations_order_db_error(self):\n        # Adding order raises DB error\n        self.order.repository.add_order.side_effect = Exception(\"fail\")\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        data_dic = {\"foo\": \"bar\"}\n        auth_dic = {}\n        error = None\n        with patch.object(\n            self.order,\n            \"_add_authorizations_to_db\",\n            wraps=self.order._add_authorizations_to_db,\n        ) as mock_add_authz:\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                result = self.order._add_order_and_authorizations(\n                    data_dic, auth_dic, payload, error\n                )\n                self.assertEqual(\n                    result, \"urn:ietf:params:acme:error:malformed\"\n                )  # error is set to 'malformed' if oid is None\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to add order: fail\",\n                log_cm.output,\n            )\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations() ended with urn:ietf:params:acme:error:malformed\",\n                log_cm.output,\n            )\n            mock_add_authz.assert_called_once_with(None, payload, auth_dic)\n\n    def test_053_add_order_and_authorizations_authz_db_error(self):\n        # Adding authorization raises DB error\n        self.order.repository.add_order.return_value = \"oid\"\n        self.order.repository.add_authorization.side_effect = Exception(\"fail\")\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        data_dic = {\"foo\": \"bar\"}\n        auth_dic = {}\n        error = None\n        # Patch _add_authorizations_to_db to call the real method\n        with patch.object(\n            self.order,\n            \"_add_authorizations_to_db\",\n            wraps=self.order._add_authorizations_to_db,\n        ) as mock_add_authz:\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                result = self.order._add_order_and_authorizations(\n                    data_dic, auth_dic, payload, error\n                )\n                self.assertIsNone(result)  # error is None, but DB error is logged\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to add authorization: fail\",\n                log_cm.output,\n            )\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations() ended with None\",\n                log_cm.output,\n            )\n            mock_add_authz.assert_called_once()\n\n    def test_054_add_order_and_authorizations_with_error_input(self):\n        # If error is already set, should skip adding order/authorizations\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        data_dic = {\"foo\": \"bar\"}\n        auth_dic = {}\n        error = \"someerror\"\n        with patch.object(\n            self.order,\n            \"_add_authorizations_to_db\",\n            wraps=self.order._add_authorizations_to_db,\n        ) as mock_add_authz:\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                result = self.order._add_order_and_authorizations(\n                    data_dic, auth_dic, payload, error\n                )\n                self.assertEqual(\n                    result, \"someerror\"\n                )  # Should return the existing error\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations()\", log_cm.output\n            )\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._add_order_and_authorizations() ended with someerror\",\n                log_cm.output,\n            )\n            mock_add_authz.assert_not_called()\n\n    def test_055_add_order_and_authorizations_logging(self):\n        self.order.repository.add_order.return_value = \"oid\"\n        self.order.repository.add_authorization.return_value = None\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        data_dic = {\"foo\": \"bar\"}\n        auth_dic = {}\n        error = None\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.order._add_order_and_authorizations(data_dic, auth_dic, payload, error)\n\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_order_and_authorizations()\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_order_and_authorizations() ended with None\",\n            log_cm.output,\n        )\n\n    def test_056_load_header_info_config_valid(self):\n        config_dic = {\"Order\": {\"header_info_list\": '[\"X-Header1\", \"X-Header2\"]'}}\n        self.order.config.header_info_list = []\n        self.order._load_header_info_config(config_dic)\n        self.assertEqual(self.order.config.header_info_list, [\"X-Header1\", \"X-Header2\"])\n\n    def test_057_load_header_info_config_invalid_json(self):\n        config_dic = {\"Order\": {\"header_info_list\": \"notjson\"}}\n        with patch.object(self.order.logger, \"warning\") as mock_warn:\n            self.order._load_header_info_config(config_dic)\n            mock_warn.assert_called()\n\n    def test_058_load_header_info_config_missing_key(self):\n        config_dic = {\"Order\": {}}\n        self.order.config.header_info_list = [\"shouldnotchange\"]\n        self.order._load_header_info_config(config_dic)\n        self.assertEqual(self.order.config.header_info_list, [\"shouldnotchange\"])\n\n    def test_059_load_header_info_config_logging(self):\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            config_dic = {\"Order\": {\"header_info_list\": '[\"X-Header1\"]'}}\n            self.order._load_header_info_config(config_dic)\n            config_dic = {\"Order\": {\"header_info_list\": \"notjson\"}}\n            self.order._load_header_info_config(config_dic)\n\n        self.assertIn(\"DEBUG:test_a2c:Order._load_header_info_config()\", log_cm.output)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._load_header_info_config() ended\", log_cm.output\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse header_info_list from configuration: Expecting value: line 1 column 1 (char 0)\",\n            log_cm.output,\n        )\n\n    def test_060_load_order_config_all_options(self):\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Challenge\")\n        config_dic.set(\"Challenge\", \"sectigo_sim\", \"True\")\n        config_dic.add_section(\"Order\")\n        config_dic.set(\"Order\", \"tnauthlist_support\", \"True\")\n        config_dic.set(\"Order\", \"email_identifier_support\", \"True\")\n        config_dic.set(\"Order\", \"email_identifier_rewrite\", \"True\")\n        config_dic.set(\"Order\", \"expiry_check_disable\", \"True\")\n        config_dic.set(\"Order\", \"idempotent_finalize\", \"True\")\n        config_dic.set(\"Order\", \"retry_after_timeout\", \"123\")\n        config_dic.set(\"Order\", \"validity\", \"456\")\n        config_dic.set(\"Order\", \"identifier_limit\", \"7\")\n        self.order._load_order_config(config_dic)\n        self.assertTrue(self.order.config.sectigo_sim)\n        self.assertTrue(self.order.config.tnauthlist_support)\n        self.assertTrue(self.order.config.email_identifier_support)\n        self.assertTrue(self.order.config.email_identifier_rewrite)\n        self.assertTrue(self.order.config.expiry_check_disable)\n        self.assertTrue(self.order.config.idempotent_finalize)\n        self.assertEqual(self.order.config.retry_after, 123)\n        self.assertEqual(self.order.config.validity, 456)\n        self.assertEqual(self.order.config.identifier_limit, 7)\n\n    def test_061_load_order_config_invalid_ints(self):\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Challenge\")\n        config_dic.set(\"Challenge\", \"sectigo_sim\", \"True\")\n        config_dic.add_section(\"Order\")\n        config_dic.set(\"Order\", \"retry_after_timeout\", \"notint\")\n        config_dic.set(\"Order\", \"validity\", \"notint\")\n        config_dic.set(\"Order\", \"identifier_limit\", \"notint\")\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n            self.order._load_order_config(config_dic)\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse retry_after from configuration: notint\",\n            log_cm.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse validity from configuration: notint\",\n            log_cm.output,\n        )\n        self.assertIn(\n            \"WARNING:test_a2c:Failed to parse identifier_limit from configuration: notint\",\n            log_cm.output,\n        )\n\n    def test_062_load_order_config_missing_sections(self):\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        # Should not raise, should use fallbacks\n        self.order._load_order_config(config_dic)\n        # All config values should remain at their defaults\n        self.assertEqual(self.order.config.retry_after, 600)\n        self.assertEqual(self.order.config.validity, 86400)\n        self.assertEqual(self.order.config.identifier_limit, 20)\n\n    def test_063_create_order_invalid_identifiers(self):\n        # Identifiers are invalid, triggers error path\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        account_name = \"acct\"\n        with patch.object(\n            self.order,\n            \"_check_identifiers_validity\",\n            return_value=(\"rejectedidentifier\", None),\n        ) as mock_check, patch.object(\n            self.order, \"_add_order_and_authorizations\", return_value=None\n        ) as mock_add_order_authz:\n            error, _detail, _order_name, _auth_dic, _expires = self.order.create_order(\n                payload, account_name\n            )\n            self.assertIsNone(error)  # _add_order_and_authorizations returns None\n            mock_check.assert_called_once()\n            mock_add_order_authz.assert_called_once()\n\n    def test_064_create_order_profile_invalid(self):\n        # Profile is present but invalid, triggers error path\n        payload = {\n            \"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}],\n            \"profile\": \"foo\",\n        }\n        account_name = \"acct\"\n        with patch.object(\n            self.order, \"_check_identifiers_validity\", return_value=(None, None)\n        ) as mock_check, patch.object(\n            self.order,\n            \"add_profile_to_order\",\n            return_value=(\n                \"invalidprofile\",\n                {\n                    \"status\": 2,\n                    \"expires\": 1234567890,\n                    \"account\": account_name,\n                    \"name\": \"randomstring\",\n                    \"identifiers\": '[{\"type\": \"dns\", \"value\": \"example.com\"}]',\n                },\n            ),\n        ) as mock_add_profile, patch.object(\n            self.order, \"_add_order_and_authorizations\", return_value=None\n        ) as mock_add_order_authz:\n            error, _detail, _order_name, _auth_dic, _expires = self.order.create_order(\n                payload, account_name\n            )\n            self.assertIsNone(error)\n            mock_check.assert_called_once()\n            mock_add_profile.assert_called_once()\n            mock_add_order_authz.assert_called_once()\n\n    def test_065_create_order_add_order_and_authz_error(self):\n        # Error occurs in _add_order_and_authorizations\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        account_name = \"acct\"\n        with patch.object(\n            self.order, \"_check_identifiers_validity\", return_value=(None, None)\n        ) as mock_check, patch.object(\n            self.order, \"_add_order_and_authorizations\", return_value=\"someerror\"\n        ) as mock_add_order_authz:\n            error, _detail, _order_name, _auth_dic, _expires = self.order.create_order(\n                payload, account_name\n            )\n            self.assertEqual(error, \"someerror\")\n            mock_check.assert_called_once()\n            mock_add_order_authz.assert_called_once()\n\n    def test_066_create_order_no_identifiers(self):\n        # Payload missing 'identifiers', triggers unsupportedidentifier error\n        payload = {\"profile\": \"foo\"}\n        account_name = \"acct\"\n        with patch(\n            \"acme_srv.order.generate_random_string\", return_value=\"randomstring\"\n        ), patch(\"acme_srv.order.uts_now\", return_value=1234567890):\n            error, detail, order_name, auth_dic, expires = self.order.create_order(\n                payload, account_name\n            )\n            self.assertEqual(error, \"urn:ietf:params:acme:error:unsupportedIdentifier\")\n            self.assertEqual(order_name, \"randomstring\")\n            self.assertIsInstance(auth_dic, dict)\n            self.assertEqual(expires, \"2009-02-14T23:31:30Z\")\n            self.assertFalse(detail)\n\n    def test_067_create_order_logging(self):\n        # Check all log messages with severity INFO and higher\n        # Use unified logger and log_stream\n        with patch(\n            \"acme_srv.helper.generate_random_string\", return_value=\"randomstring\"\n        ), patch(\"acme_srv.helper.uts_now\", return_value=1234567890), patch(\n            \"acme_srv.helper.uts_to_date_utc\", return_value=\"2026-01-01T00:00:00Z\"\n        ):\n            with patch.object(\n                self.order, \"_check_identifiers_validity\", return_value=(None, None)\n            ), patch.object(\n                self.order,\n                \"add_profile_to_order\",\n                return_value=(\n                    None,\n                    {\n                        \"status\": 2,\n                        \"expires\": 1234567890,\n                        \"account\": \"acct\",\n                        \"name\": \"randomstring\",\n                        \"identifiers\": '[{\"type\": \"dns\", \"value\": \"example.com\"}]',\n                        \"profile\": \"foo\",\n                    },\n                ),\n            ), patch.object(\n                self.order, \"_add_order_and_authorizations\", return_value=None\n            ):\n                payload = {\n                    \"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}],\n                    \"profile\": \"foo\",\n                }\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                    account_name = \"acct\"\n                    self.order.create_order(payload, account_name)\n                self.assertIn(\"DEBUG:test_a2c:Order.create_order(acct)\", log_cm.output)\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order.create_order() ended\", log_cm.output\n                )\n\n    def test_068_load_profile_config_all_paths(self):\n        with patch.object(self.order, \"_load_profiles_from_config\") as m1, patch.object(\n            self.order, \"_load_profiles_from_db_if_sync\"\n        ) as m2, patch.object(self.order, \"_maybe_disable_profile_check\") as m3:\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                self.order._load_profile_config({\"Order\": {}, \"CAhandler\": {}})\n                m1.assert_called_once()\n                m2.assert_called_once()\n                m3.assert_called_once()\n        self.assertIn(\"DEBUG:test_a2c:Order._load_profile_config()\", log_cm.output)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._load_profile_config() ended\", log_cm.output\n        )\n\n    def test_069_load_profile_config_all_paths(self):\n        with patch.object(self.order, \"_load_profiles_from_config\") as m1, patch.object(\n            self.order, \"_load_profiles_from_db_if_sync\"\n        ) as m2, patch.object(self.order, \"_maybe_disable_profile_check\") as m3:\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                self.order._load_profile_config({\"Order\": {}, \"CAhandler\": {}})\n                m1.assert_called_once()\n                m2.assert_called_once()\n                m3.assert_called_once()\n\n        self.assertIn(\"DEBUG:test_a2c:Order._load_profile_config()\", log_cm.output)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._load_profile_config() ended\", log_cm.output\n        )\n\n    def test_070_load_profiles_from_config_with_profiles(self):\n        # Should load profiles and set profiles_check_disable to False\n        config_dic = {\n            \"Order\": {\n                \"profiles\": '{\"acme\": \"http://foo.bar/profile1\", \"profile2\": \"http://foo.bar/profile2\", \"profile3\": \"http://foo.bar/profile3\"}'\n            }\n        }\n        self.order._load_profiles_from_config(config_dic)\n        self.assertFalse(self.order.config.profiles_check_disable)\n        self.assertEqual(\n            self.order.config.profiles,\n            {\n                \"acme\": \"http://foo.bar/profile1\",\n                \"profile2\": \"http://foo.bar/profile2\",\n                \"profile3\": \"http://foo.bar/profile3\",\n            },\n        )\n\n    def test_071_load_profiles_from_config_no_profiles(self):\n        # Should not set profiles or change profiles_check_disable\n        config_dic = {\"Order\": {}}\n        self.order.config.profiles = {\"bar\": {}}\n        self.order.config.profiles_check_disable = True\n        self.order._load_profiles_from_config(config_dic)\n        self.assertEqual(self.order.config.profiles, {\"bar\": {}})\n        self.assertTrue(self.order.config.profiles_check_disable)\n\n    def test_072_load_profiles_from_db_if_sync_profiles_sync_true(self):\n        # Should load profiles from DB if profiles_sync is set and True\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"CAhandler\")\n        config_dic.set(\"CAhandler\", \"profiles_sync\", \"True\")\n        self.order.repository.hkparameter_get = MagicMock(\n            return_value='{\"profiles\": {\"foo\": {}}}'\n        )\n        self.order._set_profiles_from_db = MagicMock()\n        self.order._load_profiles_from_db_if_sync(config_dic)\n        self.assertTrue(self.order.config.profiles_sync)\n        self.order._set_profiles_from_db.assert_called_once_with(\n            '{\"profiles\": {\"foo\": {}}}'\n        )\n\n    def test_073_load_profiles_from_db_if_sync_profiles_sync_false(self):\n        # Should not load profiles from DB if profiles_sync is False\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"CAhandler\")\n        config_dic.set(\"CAhandler\", \"profiles_sync\", \"False\")\n        self.order.repository.hkparameter_get = MagicMock()\n        self.order._set_profiles_from_db = MagicMock()\n        self.order._load_profiles_from_db_if_sync(config_dic)\n        self.assertFalse(self.order.config.profiles_sync)\n        self.order._set_profiles_from_db.assert_not_called()\n\n    def test_074_load_profiles_from_db_if_sync_no_profiles_sync(self):\n        # Should not load profiles from DB if profiles_sync key is missing\n\n        config_dic = {\"CAhandler\": {}}\n        self.order.repository.hkparameter_get = MagicMock()\n        self.order._set_profiles_from_db = MagicMock()\n        self.order._load_profiles_from_db_if_sync(config_dic)\n        self.assertFalse(\n            hasattr(self.order.config, \"profiles_sync\")\n            and self.order.config.profiles_sync\n        )\n        self.order._set_profiles_from_db.assert_not_called()\n\n    def test_075_load_profiles_from_db_if_sync_db_error(self):\n        # Should log and handle DB error\n        import configparser\n\n        self.order.repository.hkparameter_get = MagicMock(side_effect=Exception(\"fail\"))\n        self.order._set_profiles_from_db = MagicMock()\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"CAhandler\")\n        config_dic.set(\"CAhandler\", \"profiles_sync\", \"True\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            self.order._load_profiles_from_db_if_sync(config_dic)\n            self.order._set_profiles_from_db.assert_not_called()\n\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to get profile list: fail\",\n            log_cm.output,\n        )\n\n    def test_076_set_profiles_from_db_valid_json(self):\n        # Should set profiles from valid JSON string\n        self.order._set_profiles_from_db('{\"profiles\": {\"foo\": {}}}')\n        self.assertEqual(self.order.config.profiles, {\"foo\": {}})\n\n    def test_077_set_profiles_from_db_invalid_json(self):\n        # Should log error on invalid JSON\n        with patch.object(self.order.logger, \"error\") as mock_log:\n            self.order._set_profiles_from_db(\"notjson\")\n            mock_log.assert_called()\n\n    def test_078_maybe_disable_profile_check_true(self):\n        # Should set profiles_check_disable to True if config says so\n        import configparser\n\n        self.order.config.profiles = {\"foo\": {}}\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Order\")\n        config_dic.set(\"Order\", \"profiles_check_disable\", \"True\")\n        self.order._maybe_disable_profile_check(config_dic)\n        self.assertTrue(self.order.config.profiles_check_disable)\n\n    def test_079_maybe_disable_profile_check_false(self):\n        # Should set profiles_check_disable to False if config says so\n        import configparser\n\n        self.order.config.profiles = {\"foo\": {}}\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Order\")\n        config_dic.set(\"Order\", \"profiles_check_disable\", \"False\")\n        self.order._maybe_disable_profile_check(config_dic)\n        self.assertFalse(self.order.config.profiles_check_disable)\n\n    def test_080_maybe_disable_profile_check_no_profiles(self):\n        # Should not change profiles_check_disable if no profiles\n        import configparser\n\n        self.order.config.profiles = {}\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Order\")\n        config_dic.set(\"Order\", \"profiles_check_disable\", \"True\")\n        self.order.config.profiles_check_disable = False\n        self.order._maybe_disable_profile_check(config_dic)\n        self.assertFalse(self.order.config.profiles_check_disable)\n\n    def test_081_load_configuration_authz_validity_error(self):\n        # Test _load_configuration with invalid Authorization validity (should log warning)\n        # Use unified logger and log_stream\n        import configparser\n\n        with patch(\"acme_srv.order.load_config\") as mock_load_config:\n            config_dic = configparser.ConfigParser()\n            config_dic.add_section(\"Authorization\")\n            config_dic.set(\"Authorization\", \"validity\", \"notint\")\n            mock_load_config.return_value = config_dic\n            with patch.object(self.order, \"_load_order_config\"), patch.object(\n                self.order, \"_load_header_info_config\"\n            ), patch.object(self.order, \"_load_profile_config\"):\n                with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n                    self.order._load_configuration()\n            self.assertIn(\n                \"WARNING:test_a2c:Failed to parse authz validity from configuration: notint\",\n                log_cm.output,\n            )\n\n    def test_082_load_configuration_without_ordersection(self):\n        # Test _load_configuration without oder section in config (should use defaults and log warnings for missing options)\n        import configparser\n\n        with patch(\"acme_srv.order.load_config\") as mock_load_config:\n            config_dic = configparser.ConfigParser()\n            config_dic.add_section(\"CAhandler\")\n            config_dic.set(\"CAhandler\", \"foo\", \"bar\")\n            mock_load_config.return_value = config_dic\n            self.order._load_configuration()\n            # All Order config values should be at their defaults\n            self.assertEqual(self.order.config.retry_after, 600)\n            self.assertEqual(self.order.config.validity, 86400)\n            self.assertEqual(self.order.config.identifier_limit, 20)\n\n    def test_083_name_get_logging(self):\n        with patch(\n            \"acme_srv.order.parse_url\", return_value={\"path\": \"/acme/order/ord123\"}\n        ):\n            result = self.order._name_get(\"/acme/order/ord123\")\n            self.assertEqual(result, \"ord123\")\n\n    def test_084_name_get_with_slash(self):\n        # Should split and return first part if slash in order name\n        with patch(\n            \"acme_srv.order.parse_url\",\n            return_value={\"path\": \"/acme/order/ord456/extra\"},\n        ):\n            result = self.order._name_get(\"/acme/order/ord456/extra\")\n            self.assertEqual(result, \"ord456\")\n\n    def test_085_name_get_logging(self):\n        # Should log debug messages using central logger and log_stream\n        with patch(\n            \"acme_srv.order.parse_url\", return_value={\"path\": \"/acme/order/ord789\"}\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                self.order._name_get(\"/acme/order/ord789\")\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._name_get(/acme/order/ord789)\", log_cm.output\n            )\n            self.assertIn(\"DEBUG:test_a2c:Order._name_get() ended\", log_cm.output)\n\n    def test_086_are_identifiers_allowed_valid(self):\n        # Should return None for valid identifiers\n        with patch(\"acme_srv.order.validate_identifier\", return_value=True):\n            result = self.order.are_identifiers_allowed(\n                [{\"type\": \"dns\", \"value\": \"foo.com\"}]\n            )\n            self.assertEqual(result, (None, None))\n\n    def test_087_are_identifiers_allowed_invalid_type(self):\n        # Should return unsupportedidentifier for unknown type\n        with patch(\"acme_srv.order.validate_identifier\", return_value=True):\n            result = self.order.are_identifiers_allowed(\n                [{\"type\": \"foo\", \"value\": \"bar\"}]\n            )\n            self.assertEqual(\n                result,\n                (\n                    self.order.error_msg_dic[\"unsupportedidentifier\"],\n                    \"Identifier type foo not supported\",\n                ),\n            )\n\n    def test_088_are_identifiers_allowed_invalid_value(self):\n        # Should return rejectedidentifier if validate_identifier returns False\n        with patch(\"acme_srv.order.validate_identifier\", return_value=False):\n            result = self.order.are_identifiers_allowed(\n                [{\"type\": \"dns\", \"value\": \"foo.com\"}]\n            )\n            self.assertEqual(\n                result,\n                (\n                    self.order.error_msg_dic[\"rejectedidentifier\"],\n                    \"identifier value foo.com not allowed\",\n                ),\n            )\n\n    def test_089_are_identifiers_allowed_missing_type(self):\n        # Should return malformed if type is missing\n        result = self.order.are_identifiers_allowed([{\"value\": \"foo.com\"}])\n        result = self.order.are_identifiers_allowed([{\"value\": \"foo.com\"}])\n        self.assertEqual(\n            result,\n            (self.order.error_msg_dic[\"malformed\"], \"Identifier type is missing\"),\n        )\n\n    def test_090_are_identifiers_allowed_tnauthlist_and_email(self):\n        # Should allow tnauthlist and email if config enabled\n        with patch(\"acme_srv.order.validate_identifier\", return_value=True):\n            self.order.config.tnauthlist_support = True\n            self.order.config.email_identifier_support = True\n            result = self.order.are_identifiers_allowed(\n                [\n                    {\"type\": \"tnauthlist\", \"value\": \"foo\"},\n                    {\"type\": \"email\", \"value\": \"bar\"},\n                ]\n            )\n            self.assertEqual(result, (None, None))\n\n    def test_091_rewrite_email_identifiers_basic(self):\n        # Should rewrite DNS with @ to email\n        self.order.config.email_identifier_support = True\n        self.order.config.email_identifier_rewrite = True\n        input_list = [{\"type\": \"dns\", \"value\": \"foo@bar.com\"}]\n        result = self.order._rewrite_email_identifiers(input_list)\n        self.assertEqual(result[0][\"type\"], \"email\")\n        self.assertEqual(\n            result[0][\"value\"], \"foo@bar.com\"\n        )  # Additional assertion to differentiate\n\n    def test_092_rewrite_email_identifiers_no_rewrite(self):\n        # Should not rewrite if no @ in value\n        input_list = [{\"type\": \"dns\", \"value\": \"foobar.com\"}]\n        result = self.order._rewrite_email_identifiers(input_list)\n        self.assertEqual(result[0][\"type\"], \"dns\")\n        self.assertEqual(\n            result[0][\"value\"], \"foobar.com\"\n        )  # Additional assertion to differentiate\n\n    def test_093_rewrite_email_identifiers_other_types(self):\n        # Should not rewrite if type is not dns\n        input_list = [{\"type\": \"email\", \"value\": \"foo@bar.com\"}]\n        result = self.order._rewrite_email_identifiers(input_list)\n        self.assertEqual(result[0][\"type\"], \"email\")\n        self.assertEqual(\n            result[0][\"value\"], \"foo@bar.com\"\n        )  # Additional assertion to differentiate\n\n    def test_094_rewrite_email_identifiers_logging(self):\n        # Should log info and debug messages using the unified logger\n        self.order.config.email_identifier_support = True\n        self.order.config.email_identifier_rewrite = True\n        input_list = [{\"type\": \"dns\", \"value\": \"foo@bar.com\"}]\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.order._rewrite_email_identifiers(input_list)\n        self.assertIn(\n            \"INFO:test_a2c:Rewrite DNS identifier 'foo@bar.com' to email identifier\",\n            log_cm.output,\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._rewrite_email_identifiers()\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._rewrite_email_identifiers() ended\", log_cm.output\n        )\n\n    def test_095_name_get_basic(self):\n        # Should log debug messages using central logger and log_stream\n        with patch(\n            \"acme_srv.order.parse_url\", return_value={\"path\": \"/acme/order/ord123\"}\n        ):\n            with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                self.order._name_get(\"/acme/order/ord123\")\n\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._name_get(/acme/order/ord123)\", log_cm.output\n            )\n            self.assertIn(\"DEBUG:test_a2c:Order._name_get() ended\", log_cm.output)\n\n    def test_096_process_csr_all_paths(self):\n        # Covers: found, not found, error, logging\n        with patch(\"acme_srv.helper.b64_url_recode\", return_value=\"csrval\"):\n            # Found path\n            self.order._get_order_info = MagicMock(return_value={\"name\": \"order1\"})\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = \"cert1\"\n            cert_mock.enroll_and_store.return_value = (None, None)\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                    result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                    self.assertEqual(result[0], 200)\n                    # Not found path\n                    self.order._get_order_info = MagicMock(return_value=None)\n                    result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                    self.assertEqual(result[0], 400)\n\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr(order1)\", log_cm.output\n                )\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr() ended with order:order1 200:{cert1:None\",\n                    log_cm.output,\n                )\n\n    def test_097_process_csr_rejected_identifier(self):\n        # Covers: enroll_and_store returns rejectedIdentifier leading to 401\n        with patch(\"acme_srv.helper.b64_url_recode\", return_value=\"csrval\"):\n            self.order._get_order_info = MagicMock(return_value={\"name\": \"order1\"})\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = \"cert1\"\n            rej = \"urn:ietf:params:acme:error:rejectedIdentifier\"\n            cert_mock.enroll_and_store.return_value = (rej, \"detailx\")\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                    result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                    self.assertEqual(result, (401, rej, \"detailx\"))\n\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr(order1)\", log_cm.output\n                )\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr() ended with order:order1 401:{urn:ietf:params:acme:error:rejectedIdentifier:detailx\",\n                    log_cm.output,\n                )\n\n    def test_098_process_csr_serverinternal_error(self):\n        # Covers: enroll_and_store returns serverinternal leading to 500\n        with patch(\"acme_srv.helper.b64_url_recode\", return_value=\"csrval\"):\n\n            self.order._get_order_info = MagicMock(return_value={\"name\": \"order1\"})\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = \"cert1\"\n            cert_mock.enroll_and_store.return_value = (\n                self.order.error_msg_dic[\"serverinternal\"],\n                \"d\",\n            )\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                    result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                    self.assertEqual(result[0], 500)\n                    self.assertEqual(\n                        result[1], self.order.error_msg_dic[\"serverinternal\"]\n                    )\n\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr(order1)\", log_cm.output\n                )\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr() ended with order:order1 500:{urn:ietf:params:acme:error:serverInternal:d\",\n                    log_cm.output,\n                )\n\n    def test_099_process_csr_certificate_store_failure(self):\n        # Covers: store_csr returns falsy leading to 500 and CSR processing failed detail\n        with patch(\"acme_srv.helper.b64_url_recode\", return_value=\"csrval\"):\n            self.order._get_order_info = MagicMock(return_value={\"name\": \"order1\"})\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = None\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n                    result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                    self.assertEqual(result[0], 500)\n                    self.assertEqual(\n                        result[1], self.order.error_msg_dic[\"serverinternal\"]\n                    )\n                    self.assertEqual(result[2], \"CSR processing failed\")\n\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr(order1)\", log_cm.output\n                )\n                self.assertIn(\n                    \"DEBUG:test_a2c:Order._process_csr() ended with order:order1 500:{urn:ietf:params:acme:error:serverInternal:CSR processing failed\",\n                    log_cm.output,\n                )\n\n    def test_100_finalize_order_all_paths(self):\n        # Covers: ready, valid/idempotent, not ready, logging\n\n        # Ready path\n        self.order._get_order_info = MagicMock(return_value={\"status\": \"ready\"})\n        self.order.repository.order_update = MagicMock()\n        self.order._finalize_csr = MagicMock(return_value=(200, \"msg\", None, \"cert\"))\n        result = self.order._finalize_order(\"order1\", {\"csr\": \"csrval\"})\n        self.assertEqual(result[0], 200)\n        # Valid/idempotent path\n        self.order._get_order_info = MagicMock(return_value={\"status\": \"valid\"})\n        self.order.config.idempotent_finalize = True\n        self.order.repository.certificate_lookup = MagicMock(\n            return_value={\"name\": \"cert1\"}\n        )\n        result = self.order._finalize_order(\"order1\", {\"csr\": \"csrval\"})\n        self.assertEqual(result[0], 200)\n        # Not ready path\n        self.order._get_order_info = MagicMock(return_value={\"status\": \"pending\"})\n        result = self.order._finalize_order(\"order1\", {\"csr\": \"csrval\"})\n        self.assertEqual(result[0], 403)\n\n    def test_101_finalize_csr_updates_status_when_no_detail(self):\n        # When code==200 and no detail, order_status should update to valid\n        self.order.repository.order_update = MagicMock()\n        self.order._header_info_lookup = MagicMock(return_value={})\n        self.order._process_csr = MagicMock(return_value=(200, \"cert1\", None))\n        result = self.order._finalize_csr(\"order1\", {\"csr\": \"csrval\"})\n        self.assertEqual(result, (200, None, None, \"cert1\"))\n        self.order.repository.order_update.assert_called_once_with(\n            {\"name\": \"order1\", \"status\": \"valid\"}\n        )\n\n    def test_102_finalize_csr_handles_timeout(self):\n        # When certificate_name=='timeout', code is set to 200 and message=timeout\n        self.order.repository.order_update = MagicMock()\n        self.order._header_info_lookup = MagicMock(return_value={})\n        self.order._process_csr = MagicMock(return_value=(400, \"timeout\", \"pollid\"))\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            result = self.order._finalize_csr(\"order1\", {\"csr\": \"csrval\"})\n            self.assertEqual(result, (200, \"timeout\", \"pollid\", \"timeout\"))\n            self.order.repository.order_update.assert_not_called()\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr(order1)\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr() ended\", log_cm.output)\n\n    def test_103_finalize_csr_handles_rejected_identifier(self):\n        # When certificate_name=='urn:ietf:params:acme:error:rejectedIdentifier', code=401 and message set\n        self.order._header_info_lookup = MagicMock(return_value={})\n        rej = \"urn:ietf:params:acme:error:rejectedIdentifier\"\n        self.order._process_csr = MagicMock(return_value=(400, rej, \"detailx\"))\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            result = self.order._finalize_csr(\"order1\", {\"csr\": \"csrval\"})\n            self.assertEqual(result, (401, rej, \"detailx\", rej))\n            self.order.repository.order_update.assert_not_called()\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr(order1)\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr() ended\", log_cm.output)\n\n    def test_104_finalize_csr_enrollment_failed_else_branch(self):\n        # Else branch: message set to certificate_name and detail='enrollment failed'\n\n        self.order.repository.order_update = MagicMock()\n        self.order._header_info_lookup = MagicMock(return_value={})\n        self.order._process_csr = MagicMock(return_value=(400, \"error\", \"d\"))\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            result = self.order._finalize_csr(\"order1\", {\"csr\": \"csrval\"})\n            self.assertEqual(result, (400, \"error\", \"enrollment failed\", \"error\"))\n            self.order.repository.order_update.assert_not_called()\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr(order1)\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order._finalize_csr() ended\", log_cm.output)\n\n    def test_105_order_dic_create_all_paths(self):\n        # Covers: all fields, parse error, logging\n        tmp_dic = {\n            \"status\": \"pending\",\n            \"expires\": 1234567890,\n            \"notbefore\": 1234567890,\n            \"notafter\": 1234567890,\n            \"identifiers\": '[{\"type\": \"dns\", \"value\": \"foo.com\"}]',\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            result = self.order._order_dic_create(tmp_dic)\n            self.assertEqual(result[\"status\"], \"pending\")\n            self.assertEqual(result[\"expires\"], \"2009-02-13T23:31:30Z\")\n            self.assertEqual(result[\"notBefore\"], \"2009-02-13T23:31:30Z\")\n            self.assertEqual(result[\"notAfter\"], \"2009-02-13T23:31:30Z\")\n            self.assertIsInstance(result[\"identifiers\"], list)\n            # Parse error path\n            tmp_dic[\"identifiers\"] = \"notjson\"\n            result = self.order._order_dic_create(tmp_dic)\n            self.assertNotIn(\"identifiers\", result)\n\n        self.assertIn(\"DEBUG:test_a2c:Order._order_dic_create()\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order._order_dic_create() ended\", log_cm.output)\n        self.assertIn(\n            \"ERROR:test_a2c:Error while parsing the identifier notjson\", log_cm.output\n        )\n\n    def test_106_get_authorization_list_all_paths(self):\n        self.order.repository.authorization_lookup.return_value = [\n            {\"name\": \"auth1\", \"status__name\": \"valid\"}\n        ]\n        self.assertEqual(\n            self.order._get_authorization_list(\"order1\"),\n            [{\"name\": \"auth1\", \"status__name\": \"valid\"}],\n        )\n\n    def test_107_get_authorization_list_all_paths(self):\n        # DB error path\n        self.order.repository.authorization_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            result = self.order._get_authorization_list(\"order1\")\n            self.assertEqual(result, [])\n\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._get_authorization_list(order1)\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._get_authorization_list() ended\", log_cm.output\n        )\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to look up authorization list: fail\",\n            log_cm.output,\n        )\n\n    def test_108_update_validity_list_all_paths(self):\n        # Covers: all code paths, logging\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            # All valid\n            authz_list = [{\"name\": \"a\", \"status__name\": \"valid\"}]\n            order_dic = {\"status\": \"pending\", \"authorizations\": []}\n            self.order._update_validity_list(authz_list, order_dic, \"order1\")\n            # Some invalid\n            authz_list = [{\"name\": \"a\", \"status__name\": \"invalid\"}]\n            order_dic = {\"status\": \"pending\", \"authorizations\": []}\n            self.order._update_validity_list(authz_list, order_dic, \"order1\")\n            # No validities\n            authz_list = []\n            order_dic = {\"status\": \"pending\", \"authorizations\": []}\n            self.order._update_validity_list(authz_list, order_dic, \"order1\")\n        self.assertIn(\"DEBUG:test_a2c:Order._update_validity_list()\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order.get_order_details() ended\", log_cm.output)\n\n    def test_109_get_order_details_all_paths(self):\n        # Covers: found, not found, logging\n\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.order._get_order_info = MagicMock(return_value={\"status\": \"pending\"})\n            result = self.order.get_order_details(\"order1\")\n\n            self.assertEqual(result, {\"status\": \"pending\", \"authorizations\": []})\n            self.order._get_order_info = MagicMock(return_value=None)\n            result = self.order.get_order_details(\"order1\")\n            self.assertIsInstance(result, dict)\n\n        self.assertIn(\"DEBUG:test_a2c:Order.get_order_details(order1)\", log_cm.output)\n        self.assertIn(\"DEBUG:test_a2c:Order.get_order_details() ended\", log_cm.output)\n\n    def test_110_invalidate_expired_orders_all_paths(self):\n        # Covers: success, db error, logging\n        self.order.repository.orders_invalid_search.return_value = [\n            {\"name\": \"order1\", \"status__name\": \"pending\"}\n        ]\n        result = self.order.invalidate_expired_orders()\n        self.assertEqual(\n            result,\n            (\n                [\n                    \"id\",\n                    \"name\",\n                    \"expires\",\n                    \"identifiers\",\n                    \"created_at\",\n                    \"status__id\",\n                    \"status__name\",\n                    \"account__id\",\n                    \"account__name\",\n                    \"account__contact\",\n                ],\n                [{\"name\": \"order1\", \"status__name\": \"pending\"}],\n            ),\n        )\n\n    def test_111_invalidate_expired_orders_all_paths(self):\n        # DB error path\n        self.order.repository.orders_invalid_search.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            result = self.order.invalidate_expired_orders()\n            self.assertIsInstance(result, tuple)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to search for expired orders: fail\",\n            log_cm.output,\n        )\n\n    def test_112_process_order_request_all_paths(self):\n        # Covers: finalize, polling, cert found, cert not found, url missing, logging\n\n        self.order._finalize_order = MagicMock(return_value=(200, \"msg\", None, \"cert\"))\n        self.order.repository.certificate_lookup = MagicMock(\n            return_value={\"name\": \"cert1\"}\n        )\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            # Finalize path\n            result = self.order._process_order_request(\n                \"order1\", {\"url\": \"finalize\"}, {}, None\n            )\n            self.assertEqual(result[0], 200)\n            # Polling path with cert found\n            result = self.order._process_order_request(\n                \"order1\", {\"url\": \"poll\"}, {}, None\n            )\n            self.assertEqual(result[0], 200)\n            # Polling path with cert not found\n            self.order.repository.certificate_lookup = MagicMock(return_value={})\n            result = self.order._process_order_request(\n                \"order1\", {\"url\": \"poll\"}, {}, None\n            )\n            self.assertEqual(result[0], 200)\n            # url missing\n            result = self.order._process_order_request(\"order1\", {}, {}, None)\n            self.assertEqual(result[0], 400)\n\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._process_order_request({order1)\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._process_order_request() ended with order:order1 200:msg:None\",\n            log_cm.output,\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._process_order_request() ended with order:order1 400:urn:ietf:params:acme:error:malformed:url is missing in protected\",\n            log_cm.output,\n        )\n\n    def test_113_check_identifiers_validity_all_paths(self):\n        # Covers: valid, too many, malformed, email rewrite, allowed, rejected, and logging\n        with patch(\"acme_srv.order.validate_identifier\", return_value=True):\n            self.order.config.identifier_limit = 2\n            self.order.config.email_identifier_support = True\n            self.order.config.email_identifier_rewrite = True\n            # Valid identifiers, triggers rewrite\n            idents = [{\"type\": \"dns\", \"value\": \"foo@bar.com\"}]\n            result = self.order._check_identifiers_validity(idents)\n            self.assertEqual(result, (None, None))\n            # Too many identifiers\n            too_many = [{\"type\": \"dns\", \"value\": \"a\"}] * 3\n            result = self.order._check_identifiers_validity(too_many)\n            self.assertEqual(\n                result,\n                (\n                    self.order.error_msg_dic[\"rejectedidentifier\"],\n                    \"identifier limit exceeded\",\n                ),\n            )\n            # Malformed (not a list)\n            result = self.order._check_identifiers_validity(None)\n            self.assertEqual(\n                result,\n                (self.order.error_msg_dic[\"malformed\"], \"malformed identifiers list\"),\n            )\n\n    def test_114_check_identifiers_validity_all_paths(self):\n        with patch(\"acme_srv.order.validate_identifier\", return_value=False):\n            self.order.config.identifier_limit = 2\n            self.order.config.email_identifier_support = True\n            self.order.config.email_identifier_rewrite = True\n            idents = [{\"type\": \"dns\", \"value\": \"foo@bar.com\"}]\n            result = self.order._check_identifiers_validity(idents)\n            self.assertEqual(\n                result,\n                (\n                    self.order.error_msg_dic[\"rejectedidentifier\"],\n                    \"identifier value foo@bar.com not allowed\",\n                ),\n            )\n\n    def test_115_get_order_info_all_paths(self):\n        # Covers: successful lookup, DB error, logging\n        self.order.repository.order_lookup.return_value = {\"name\": \"order1\"}\n        result = self.order._get_order_info(\"order1\")\n        self.assertEqual(result, {\"name\": \"order1\"})\n\n    def test_116_get_order_info_all_paths(self):\n        # Clear log buffer before error path\n        self.order.repository.order_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            result = self.order._get_order_info(\"order1\")\n            self.assertIsNone(result)\n            self.assertIn(\n                \"CRITICAL:test_a2c:Database error: failed to look up order: fail\",\n                log_cm.output,\n            )\n\n    def test_117_header_info_lookup_all_paths(self):\n        # Covers: header present, header missing, header_info_list missing, logging\n        # Use central logger and log_stream from setUp\n\n        self.order.config.header_info_list = [\"X-Test\", \"X-Other\"]\n        # Header with matching keys\n        header = {\"X-Test\": \"foo\", \"X-Other\": \"bar\", \"X-Irrelevant\": \"baz\"}\n        result = self.order._header_info_lookup(header)\n        self.assertEqual(json.loads(result), {\"X-Test\": \"foo\", \"X-Other\": \"bar\"})\n        # Header with no matching keys\n        header = {\"X-Irrelevant\": \"baz\"}\n        result = self.order._header_info_lookup(header)\n        self.assertIsNone(result)\n        # No header_info_list\n        self.order.config.header_info_list = None\n        result = self.order._header_info_lookup({\"X-Test\": \"foo\"})\n        self.assertIsNone(result)\n\n    def test_118_enter_loads_configuration_and_returns_self(self):\n        # Covers __enter__: should call _load_configuration and return self\n        with patch.object(self.order, \"_load_configuration\") as mock_load_config:\n            result = self.order.__enter__()\n            mock_load_config.assert_called_once()\n            self.assertIs(result, self.order)\n\n    def test_119_parse_order_content_adds_certificate(self):\n        # Covers lines 976-978: certificate_name and status valid adds certificate path\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(200, None, None, {\"url\": \"url\"}, {}, \"account\"),\n        ):\n            with patch.object(\n                self.order,\n                \"_parse_order_message\",\n                return_value=(200, None, None, \"cert123\", \"order1\"),\n            ):\n                with patch.object(\n                    self.order, \"get_order_details\", return_value={\"status\": \"valid\"}\n                ):\n                    with patch.object(\n                        self.order.message,\n                        \"prepare_response\",\n                        side_effect=lambda resp, stat: resp,\n                    ):\n                        self.order.path_dic[\"cert_path\"] = \"/acme/cert/\"\n                        self.order.server_name = \"https://example.com\"\n                        result = self.order.parse_order_content(\"content\")\n                        self.assertIn(\"certificate\", result[\"data\"])\n                        self.assertEqual(\n                            result[\"data\"][\"certificate\"],\n                            \"https://example.com/acme/cert/cert123\",\n                        )\n\n    def test_120_invalidate_expired_orders_update_error_logging(self):\n        # Covers lines 831-840: order_update raises OrderDatabaseError and logs CRITICAL\n        self.order.repository.orders_invalid_search = MagicMock(\n            return_value=[{\"name\": \"order1\", \"status__name\": \"pending\"}]\n        )\n        self.order.repository.order_update = MagicMock(side_effect=Exception(\"fail\"))\n        # Run\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            self.order.invalidate_expired_orders(1234567890)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to update order status to invalid: fail\",\n            log_cm.output,\n        )\n\n    def test_121_process_csr_generic_error(self):\n        # Covers lines 681-684: error is not rejectedIdentifier or serverinternal\n        with patch.object(\n            self.order, \"_get_order_info\", return_value={\"name\": \"order1\"}\n        ):\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = \"cert1\"\n            cert_mock.enroll_and_store.return_value = (\"someerror\", \"detail\")\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                self.assertEqual(result[0], 400)\n                self.assertEqual(result[1], \"someerror\")\n\n    def test_122_process_csr_serverinternal_error(self):\n        # Covers lines 684-689: error == serverinternal triggers code=500\n        with patch.object(\n            self.order, \"_get_order_info\", return_value={\"name\": \"order1\"}\n        ):\n            cert_mock = MagicMock()\n            cert_mock.store_csr.return_value = \"cert1\"\n            cert_mock.enroll_and_store.return_value = (\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"detail\",\n            )\n            with patch(\"acme_srv.order.Certificate\") as cert_class:\n                cert_class.return_value.__enter__.return_value = cert_mock\n                result = self.order._process_csr(\"order1\", \"csr\", \"header\")\n                self.assertEqual(result[0], 500)\n                self.assertEqual(result[1], \"urn:ietf:params:acme:error:serverInternal\")\n\n    def test_123_process_order_request_db_error_logging(self):\n        # Covers: OrderDatabaseError in certificate_lookup and CRITICAL log\n        self.order.repository.certificate_lookup.side_effect = Exception(\"fail\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            # Should hit the exception path and log CRITICAL\n            result = self.order._process_order_request(\n                \"order1\", {\"url\": \"poll\"}, {}, None\n            )\n            self.assertEqual(result[0], 200)\n            self.assertIsNone(result[3])  # certificate_name should be None\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: Certificate lookup failed: fail\",\n            log_cm.output,\n        )\n\n    def test_124_process_order_request_no_url(self):\n        # Covers lines 634-638: protected dict missing 'url' and checks log\n        with patch.object(self.order.logger, \"debug\") as mock_debug:\n            result = self.order._process_order_request(\"ordername\", {}, {}, None)\n            self.assertEqual(result[0], 400)\n            self.assertEqual(result[1], \"urn:ietf:params:acme:error:malformed\")\n            self.assertEqual(result[2], \"url is missing in protected\")\n            self.assertIsNone(result[3])\n            mock_debug.assert_any_call(\n                \"Order._process_order_request() ended with order:%s %s:%s:%s\",\n                \"ordername\",\n                400,\n                \"urn:ietf:params:acme:error:malformed\",\n                \"url is missing in protected\",\n            )\n\n    def test_125_finalize_order_valid_OrderDatabaseError(self):\n        # Covers lines 593-597: status not ready\n        self.order.repository.order_lookup.return_value = {\"status\": \"valid\"}\n        self.order.config.idempotent_finalize = True\n        self.order.repository.certificate_lookup.side_effect = Exception(\"db error\")\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            result = self.order._finalize_order(\"ordername\", {}, None)\n            self.assertEqual(result[0], 200)\n            self.assertIsNone(result[1])\n            self.assertIsNone(result[2])\n            self.assertIsNone(result[3])\n        # Extract critical log messages from the log stream and check for the expected message\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: Certificate lookup failed: db error\",\n            log_cm.output,\n        )\n\n    def test_126_finalize_order_ready_nocsr(self):\n        # Covers lines 593-597: status not ready\n        self.order.repository.order_lookup.return_value = {\"status\": \"ready\"}\n        result = self.order._finalize_order(\"ordername\", {}, None)\n        self.assertEqual(result[0], 400)\n        self.assertEqual(result[1], \"urn:ietf:params:acme:error:badCSR\")\n        self.assertEqual(result[2], \"csr is missing in payload\")\n        self.assertIsNone(result[3])\n\n    def test_127_finalize_csr_timeout(self):\n        # Patch _process_csr to return (200, 'timeout', 'not_none') so the elif branch is taken\n        with patch.object(\n            self.order, \"_process_csr\", return_value=(400, \"timeout\", \"not_none\")\n        ):\n            result = self.order._finalize_csr(\n                \"ordername\", {\"csr\": \"csrdata\"}, header=None\n            )\n            self.assertEqual(result[0], 200)\n            self.assertEqual(result[1], \"timeout\")\n            self.assertEqual(result[2], \"not_none\")\n            self.assertEqual(result[3], \"timeout\")\n\n    def test_128_from_content_rejectedidentifier_with_detail(self):\n        # Ensure the 'rejectedidentifier' error branch is covered\n        rejected = self.order.error_msg_dic[\"rejectedidentifier\"]\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(\n                200,\n                None,\n                None,\n                None,\n                {\"identifiers\": [{\"type\": \"dns\", \"value\": \"a\"}]},\n                \"account\",\n            ),\n        ):\n            with patch.object(\n                self.order,\n                \"create_order\",\n                return_value=(rejected, \"detail\", \"order\", {}, \"2026-01-01T00:00:00Z\"),\n            ):\n                with patch.object(\n                    self.order.message,\n                    \"prepare_response\",\n                    side_effect=lambda resp, stat: {**resp, **stat},\n                ):\n                    result = self.order.create_from_content(\"content\")\n                    self.assertEqual(result[\"code\"], 403)\n                    self.assertEqual(result[\"type\"], rejected)\n                    self.assertEqual(result[\"detail\"], \"detail\")\n\n    def test_129_from_content_rejectedidentifier_without_detail(self):\n        # Ensure the 'rejectedidentifier' error branch is covered\n        rejected = self.order.error_msg_dic[\"rejectedidentifier\"]\n        with patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(\n                200,\n                None,\n                None,\n                None,\n                {\"identifiers\": [{\"type\": \"dns\", \"value\": \"a\"}]},\n                \"account\",\n            ),\n        ):\n            with patch.object(\n                self.order,\n                \"create_order\",\n                return_value=(rejected, None, \"order\", {}, \"2026-01-01T00:00:00Z\"),\n            ):\n                with patch.object(\n                    self.order.message,\n                    \"prepare_response\",\n                    side_effect=lambda resp, stat: {**resp, **stat},\n                ):\n                    result = self.order.create_from_content(\"content\")\n                    self.assertEqual(result[\"code\"], 403)\n                    self.assertEqual(result[\"type\"], rejected)\n                    self.assertEqual(\n                        result[\"detail\"],\n                        \"Some of the requested identifiers got rejected\",\n                    )\n\n    def test_130_apply_eab_profile_eab_profiling_disabled(self):\n        self.order.config.eab_profiling = False\n        with patch.object(self.order, \"_apply_eab_profile\") as mock_apply_eab:\n            payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n            account_name = \"acct\"\n            with patch.object(\n                self.order, \"_check_identifiers_validity\", return_value=(None, None)\n            ), patch.object(\n                self.order, \"_add_order_and_authorizations\", return_value=None\n            ):\n                self.order.create_order(payload, account_name)\n                mock_apply_eab.assert_not_called()\n\n    def test_131_apply_eab_profile_account_lookup_db_error(self):\n        self.order.config.eab_profiling = True\n        self.order.repository.account_lookup.side_effect = Exception(\"fail\")\n        with patch.object(self.order.logger, \"critical\") as mock_critical:\n            self.order._apply_eab_profile(\"acct\")\n            mock_critical.assert_called()\n\n    def test_132_apply_eab_profile_no_eab_kid(self):\n        self.order.config.eab_profiling = True\n        self.order.repository.account_lookup.return_value = {}\n        with patch.object(self.order.logger, \"debug\") as mock_debug:\n            self.order._apply_eab_profile(\"acct\")\n            mock_debug.assert_any_call(\n                \"Order._apply_eab_profile() - apply eab profile setting for account %s\",\n                \"acct\",\n            )\n\n    def test_133_apply_eab_profile_allowed_domainlist_order_section(self):\n        self.order.config.eab_profiling = True\n        self.order.repository.account_lookup.return_value = {\"eab_kid\": \"kid1\"}\n        mock_eab_handler = MagicMock()\n        profile_dic = {\"kid1\": {\"order\": {\"allowed_domainlist\": [\"example.com\"]}}}\n        mock_eab_handler.__enter__.return_value.key_file_load.return_value = profile_dic\n        self.order.config.eab_handler = MagicMock(return_value=mock_eab_handler)\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            self.order._apply_eab_profile(\"acct\")\n            self.assertIn(\n                \"DEBUG:test_a2c:Order._apply_eab_profile() - apply eab profile setting for account acct\",\n                log_cm.output,\n            )\n        self.assertEqual(self.order.config.allowed_domainlist, [\"example.com\"])\n\n    def test_134_apply_eab_profile_allowed_domainlist_cahandler_section(self):\n        self.order.config.eab_profiling = True\n        self.order.repository.account_lookup.return_value = {\"eab_kid\": \"kid2\"}\n        mock_eab_handler = MagicMock()\n        profile_dic = {\"kid2\": {\"cahandler\": {\"allowed_domainlist\": [\"test.com\"]}}}\n        mock_eab_handler.__enter__.return_value.key_file_load.return_value = profile_dic\n        self.order.config.eab_handler = MagicMock(return_value=mock_eab_handler)\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n            self.order._apply_eab_profile(\"acct\")\n            self.assertIn(\n                \"WARNING:test_a2c:allowed_domainlist parameter found in cahandler section of the eab-profile - this is deprecated, please use the order section\",\n                log_cm.output,\n            )\n        self.assertEqual(self.order.config.allowed_domainlist, [\"test.com\"])\n\n    def test_135_apply_eab_profile_generic_exception(self):\n        self.order.config.eab_profiling = True\n        self.order.repository.account_lookup.return_value = {\"eab_kid\": \"kid3\"}\n        mock_eab_handler = MagicMock()\n        mock_eab_handler.__enter__.return_value.key_file_load.side_effect = Exception(\n            \"fail\"\n        )\n        self.order.config.eab_handler = MagicMock(return_value=mock_eab_handler)\n        with self.assertLogs(\"test_a2c\", level=\"WARNING\") as log_cm:\n            self.order._apply_eab_profile(\"acct\")\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to process EAB profile for Account acct (kid: kid3): fail\",\n            log_cm.output,\n        )\n\n    def test_136_create_order_eab_profiling_branch(self):\n        # Covers: if self.config.eab_profiling and self.config.eab_handler\n        self.order.config.eab_profiling = True\n        self.order.config.eab_handler = MagicMock()\n        with patch.object(self.order, \"_apply_eab_profile\") as mock_apply_eab:\n            payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n            account_name = \"acct\"\n            with patch.object(\n                self.order, \"_check_identifiers_validity\", return_value=(None, None)\n            ), patch.object(\n                self.order, \"_add_order_and_authorizations\", return_value=None\n            ):\n                self.order.create_order(payload, account_name)\n                mock_apply_eab.assert_called_once_with(account_name)\n\n    def test_137_create_order_invalid_profile_detail(self):\n        # Covers: if error == self.error_msg_dic[\"invalidprofile\"]: detail = \"Invalid profile specified\"\n        self.order.config.eab_profiling = False\n        self.order.config.eab_handler = None\n        self.order.config.profiles = {\"bar\": {}}\n        payload = {\n            \"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}],\n            \"profile\": \"foo\",\n        }\n        account_name = \"acct\"\n        with patch.object(\n            self.order, \"_check_identifiers_validity\", return_value=(None, None)\n        ), patch.object(\n            self.order,\n            \"add_profile_to_order\",\n            return_value=(self.order.error_msg_dic[\"invalidprofile\"], {}),\n        ), patch.object(\n            self.order, \"_add_order_and_authorizations\", return_value=None\n        ):\n            error, detail, order_name, auth_dic, expires = self.order.create_order(\n                payload, account_name\n            )\n            self.assertIsNone(error)\n            self.assertEqual(detail, \"Invalid profile specified\")\n\n    def test_138_are_identifiers_allowed_fqdn_not_whitelisted(self):\n        # Covers: FQDN/SAN not allowed by configuration (lines 551-566)\n        with patch(\"acme_srv.order.validate_identifier\", return_value=True), patch(\n            \"acme_srv.order.is_domain_whitelisted\", return_value=False\n        ):\n            self.order.config.allowed_domainlist = [\"allowed.com\"]\n            result = self.order.are_identifiers_allowed(\n                [{\"type\": \"dns\", \"value\": \"notallowed.com\"}]\n            )\n            self.assertEqual(\n                result,\n                (\n                    self.order.error_msg_dic[\"rejectedidentifier\"],\n                    \"FQDN/SAN notallowed.com not allowed by configuration\",\n                ),\n            )\n\n    def test_139_apply_eab_profile_disabled(self):\n        # Covers: logger.critical branch in _apply_eab_profile (line 270)\n        self.order.config.eab_profiling = False\n        self.order.config.eab_handler = MagicMock()\n        with patch.object(\n            self.order.repository, \"account_lookup\"\n        ) as mock_account_lookup, patch.object(\n            self.order.logger, \"critical\"\n        ) as mock_critical:\n            self.assertFalse(self.order._apply_eab_profile(\"acct\"))\n            self.assertFalse(mock_account_lookup.called)\n            self.assertFalse(mock_critical.called)\n\n    def test_140_check_single_identifier_missing_type(self):\n        # Covers error message for missing 'type' (line 556)\n        identifier = {\"value\": \"bar\"}\n        allowed_identifiers = [\"dns\", \"ip\"]\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n            error, detail = self.order._check_single_identifier(\n                identifier, allowed_identifiers\n            )\n        self.assertEqual(error, self.order.error_msg_dic[\"malformed\"])\n        self.assertEqual(detail, \"Identifier type is missing\")\n        self.assertIn(\"ERROR:test_a2c:Identifier type is missing\", log_cm.output)\n\n    def test_141_check_single_identifier_wrong_type(self):\n        # Covers error message for missing 'type' (line 556)\n        identifier = {\"type\": \"unknown\", \"value\": \"bar\"}\n        allowed_identifiers = [\"dns\", \"ip\"]\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n            error, detail = self.order._check_single_identifier(\n                identifier, allowed_identifiers\n            )\n            self.assertEqual(error, self.order.error_msg_dic[\"unsupportedidentifier\"])\n            self.assertEqual(detail, \"Identifier type unknown not supported\")\n        self.assertIn(\n            \"ERROR:test_a2c:Identifier type unknown not supported\", log_cm.output\n        )\n\n    def test_142_check_single_identifier_invalid_value(self):\n        # Covers error message for invalid value (line 571)\n        identifier = {\"type\": \"dns\", \"value\": \"foo\"}\n        allowed_identifiers = [\"dns\", \"ip\"]\n        with patch(\"acme_srv.order.validate_identifier\", return_value=False):\n            with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n                error, detail = self.order._check_single_identifier(\n                    identifier, allowed_identifiers\n                )\n            self.assertEqual(error, self.order.error_msg_dic[\"rejectedidentifier\"])\n            self.assertEqual(detail, \"identifier value foo not allowed\")\n            self.assertIn(\n                \"ERROR:test_a2c:Identifier value foo not allowed for type dns\",\n                log_cm.output,\n            )\n\n    def test_143_add_authorizations_to_db_success(self):\n        # Test normal case: authorizations added successfully\n        self.order.repository.add_authorization.return_value = None\n        self.order.config.authz_validity = 1000\n        oid = \"order123\"\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        auth_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            error = self.order._add_authorizations_to_db(oid, payload, auth_dic)\n            self.assertIsNone(error)\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_authorizations_to_db(order123)\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_authorizations_to_db() ended with None\",\n            log_cm.output,\n        )\n        self.assertIn(\"order\", payload[\"identifiers\"][0])\n        self.assertEqual(payload[\"identifiers\"][0][\"status\"], \"pending\")\n        self.assertIn(list(auth_dic.keys())[0], auth_dic)\n\n    def test_144_add_authorizations_to_db_malformed(self):\n        # Test malformed case: oid is None\n        oid = None\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        auth_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            error = self.order._add_authorizations_to_db(oid, payload, auth_dic)\n            self.assertEqual(error, self.order.error_msg_dic[\"malformed\"])\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_authorizations_to_db(None)\", log_cm.output\n        )\n        self.assertIn(\n            f\"DEBUG:test_a2c:Order._add_authorizations_to_db() ended with {self.order.error_msg_dic['malformed']}\",\n            log_cm.output,\n        )\n\n    def test_145_add_authorizations_to_db_db_error(self):\n        # Test DB error: add_authorization raises exception\n        self.order.repository.add_authorization.side_effect = Exception(\"fail\")\n        oid = \"order123\"\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        auth_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"CRITICAL\") as log_cm:\n            error = self.order._add_authorizations_to_db(oid, payload, auth_dic)\n            self.assertIsNone(error)  # error is not set in DB error, just logged\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to add authorization: fail\",\n            log_cm.output,\n        )\n\n    def test_146_add_authorizations_to_db_sectigo_sim(self):\n        # Covers sectigo_sim branch: status set to valid and update_authorization called\n        self.order.config.sectigo_sim = True\n        self.order.repository.add_authorization.return_value = None\n        self.order.repository.update_authorization.return_value = None\n        oid = \"order123\"\n        payload = {\"identifiers\": [{\"type\": \"dns\", \"value\": \"example.com\"}]}\n        auth_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as log_cm:\n            error = self.order._add_authorizations_to_db(oid, payload, auth_dic)\n            self.assertIsNone(error)\n        self.assertEqual(payload[\"identifiers\"][0][\"status\"], \"valid\")\n        self.order.repository.update_authorization.assert_called_once_with(\n            payload[\"identifiers\"][0]\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_authorizations_to_db(order123)\", log_cm.output\n        )\n        self.assertIn(\n            \"DEBUG:test_a2c:Order._add_authorizations_to_db() ended with None\",\n            log_cm.output,\n        )\n\n    def test_147_create_from_content_malformed_identifier(self):\n        # Covers the branch where error == self.order.error_msg_dic[\"malformed\"] and detail is None\n        malformed_error = self.order.error_msg_dic[\"malformed\"]\n        with patch.object(\n            self.order,\n            \"create_order\",\n            return_value=(\n                malformed_error,\n                None,\n                \"ordername\",\n                {},\n                \"2026-01-01T00:00:00Z\",\n            ),\n        ), patch.object(\n            self.order.message,\n            \"check\",\n            return_value=(200, None, None, None, {}, \"acct\"),\n        ), patch.object(\n            self.order.message,\n            \"prepare_response\",\n            side_effect=lambda resp, status: {**resp, **status},\n        ):\n            response = self.order.create_from_content(\"dummycontent\")\n            self.assertEqual(response[\"code\"], 400)\n            self.assertEqual(response[\"type\"], malformed_error)\n            self.assertEqual(\n                response[\"detail\"], \"One of the requested identifiers is not supported\"\n            )\n\n    def test_148_load_configuration_directory_url_prefix(self):\n        # Covers the Directory/url_prefix branch in _load_configuration (line 513)\n        import configparser\n\n        config_dic = configparser.ConfigParser()\n        config_dic.add_section(\"Directory\")\n        config_dic.set(\"Directory\", \"url_prefix\", \"/prefix/\")\n        config_dic.add_section(\"Order\")\n        config_dic.add_section(\"Authorization\")\n        with patch(\"acme_srv.order.load_config\", return_value=config_dic):\n            self.order.path_dic = {\n                \"authz_path\": \"/acme/authz/\",\n                \"order_path\": \"/acme/order/\",\n                \"cert_path\": \"/acme/cert/\",\n            }\n            self.order._load_configuration()\n            self.assertTrue(\n                all(v.startswith(\"/prefix/\") for v in self.order.path_dic.values())\n            )\n\n    def test_149_check_single_identifier_missing_value(self):\n        # Covers lines 578-579: missing 'value' in identifier\n        identifier = {\"type\": \"dns\"}\n        allowed_identifiers = [\"dns\", \"ip\"]\n        with self.assertLogs(\"test_a2c\", level=\"ERROR\") as log_cm:\n            error, detail = self.order._check_single_identifier(\n                identifier, allowed_identifiers\n            )\n        self.assertEqual(error, self.order.error_msg_dic[\"malformed\"])\n        self.assertEqual(detail, \"Identifier value is missing\")\n        self.assertIn(\"ERROR:test_a2c:Identifier value is missing\", log_cm.output)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_pkcs7_soap_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\n# pylint: disable= C0415, W0212\nimport unittest\nimport sys\nimport os\nfrom unittest.mock import patch, Mock, mock_open\nimport base64\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography import x509\n\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from examples.ca_handler.pkcs7_soap_ca_handler import (\n            CAhandler,\n            binary_read,\n            binary_write,\n        )\n\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n        self.binary_read = binary_read\n        self.binary_write = binary_write\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_002_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler section is missing in configuration file.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_003_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"foo\": \"bar\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_004_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"email\": \"email\", \"foo\": \"bar\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertEqual(\"email\", self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_005_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"soap_srv\": \"soap_srv\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"soap_srv\", self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_006_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"profilename\": \"profilename\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertEqual(\"profilename\", self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_007_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load no cahandler section\"\"\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"ca_bundle\": \"ca_bundle\"}}\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertEqual(\"ca_bundle\", self.cahandler.ca_bundle)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_008_config_load(self, mock_load_cfg, mock_file):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = False\n        mock_load_cfg.return_value = {\"CAhandler\": {\"signing_cert\": \"signing_cert\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate file not found: signing_cert\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.x509.load_pem_x509_certificate\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_009_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_cert\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"signing_cert\": \"signing_cert\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertEqual(\"signing_cert\", self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_010_config_load(self, mock_load_cfg, mock_file):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = False\n        mock_load_cfg.return_value = {\"CAhandler\": {\"password\": \"password\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertEqual(\"password\".encode(\"utf8\"), self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_011_config_load(self, mock_load_cfg, mock_file):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = False\n        mock_load_cfg.return_value = {\"CAhandler\": {\"signing_key\": \"signing_key\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing key file not found: signing_key\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_012_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"signing_key\": \"signing_key\"}}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertEqual(\"signing_key\", self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertFalse(self.cahandler.signing_script_dic)\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:Signing certificate option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_013_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\"CAhandler\": {\"signing_script\": \"signing_script\"}}\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\"signing_script\": \"signing_script\"}, self.cahandler.signing_script_dic\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_014_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_alias\": \"signing_alias\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\"signing_alias\": \"signing_alias\", \"signing_script\": \"signing_script\"},\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_015_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_csr_path\": \"signing_csr_path\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\n                \"signing_csr_path\": \"signing_csr_path\",\n                \"signing_script\": \"signing_script\",\n            },\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_016_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_config_variant\": \"signing_config_variant\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\n                \"signing_script\": \"signing_script\",\n                \"signing_config_variant\": \"signing_config_variant\",\n            },\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_017_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_user\": \"signing_user\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\"signing_script\": \"signing_script\", \"signing_user\": \"signing_user\"},\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_018_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_sleep_timer\": \"signing_sleep_timer\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\n                \"signing_script\": \"signing_script\",\n                \"signing_sleep_timer\": \"signing_sleep_timer\",\n            },\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    @patch(\"os.path.exists\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.load_config\")\n    def test_019_config_load(self, mock_load_cfg, mock_file, mock_load):\n        \"\"\"test _config_load signing cert configured but does not exist\"\"\"\n        mock_file.return_value = True\n        mock_load.return_value = \"signing_key\"\n        mock_load_cfg.return_value = {\n            \"CAhandler\": {\n                \"signing_script\": \"signing_script\",\n                \"signing_interpreter\": \"signing_interpreter\",\n            }\n        }\n        self.maxDiff = None\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.soap_srv)\n        self.assertFalse(self.cahandler.profilename)\n        self.assertFalse(self.cahandler.email)\n        self.assertFalse(self.cahandler.signing_cert)\n        self.assertFalse(self.cahandler.signing_key)\n        self.assertFalse(self.cahandler.password)\n        self.assertEqual(\n            {\n                \"signing_script\": \"signing_script\",\n                \"signing_interpreter\": \"signing_interpreter\",\n            },\n            self.cahandler.signing_script_dic,\n        )\n        error_buffer = [\n            \"ERROR:test_a2c:SOAP server URL (soap_srv) is missing in configuration file.\",\n            \"WARNING:test_a2c:SOAP server certificate validation is disabled.\",\n            \"ERROR:test_a2c:Profile name (profilename) is missing in configuration file.\",\n            \"ERROR:test_a2c:Email option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_alias option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_csr_path option is missing in configuration file.\",\n            \"ERROR:test_a2c:signing_config_variant option is missing in configuration file.\",\n        ]\n        self.assertEqual(error_buffer, lcm.output)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._config_load\")\n    def test_020_enter(self, mock_cfgload):\n        \"\"\"enter - no soap server configured\"\"\"\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfgload.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._config_load\")\n    def test_021_enter(self, mock_cfgload):\n        \"\"\"enter soap server configured\"\"\"\n        self.cahandler.soap_srv = \"mock_srv\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfgload.called)\n\n    def test_022_exit(self):\n        \"\"\"enter - no soap server configured\"\"\"\n        self.cahandler.__exit__()\n\n    @patch(\"pyasn1.codec.der.decoder.decode\")\n    def test_023_cert_decode(self, mock_der):\n        \"\"\"test _cert_decode()\"\"\"\n        mock_der.return_value = \"decode\"\n        cert = Mock()\n        cert.public_bytes = Mock()\n        self.assertEqual(\"decode\", self.cahandler._cert_decode(cert))\n\n    def test_024_poll(self):\n        \"\"\"test poll\"\"\"\n        self.assertEqual(\n            (None, None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_025_revoke(self):\n        \"\"\"test revoke\"\"\"\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"Revocation is not supported.\",\n            ),\n            self.cahandler.revoke(\"cert_name\", \"reason\", \"date\"),\n        )\n\n    def test_026_trigger(self):\n        \"\"\"test revoke\"\"\"\n        self.assertEqual((None, None, None), self.cahandler.trigger(\"identifier\"))\n\n    def test_027_soaprequest_build(self):\n        \"\"\"test soap request build\"\"\"\n        self.cahandler.profilename = \"profilename\"\n        self.cahandler.email = \"email\"\n        pkcs7 = \"pkcs7\"\n        result = \"\"\"\n<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:aur=\"http://monetplus.cz/services/kb/aurora\">\n<soapenv:Header/>\n<soapenv:Body>\n    <aur:RequestCertificate>\n        <aur:request>\n            <aur:ProfileName>profilename</aur:ProfileName>\n            <aur:CertificateRequestRaw>pkcs7</aur:CertificateRequestRaw>\n            <aur:Email>email</aur:Email>\n            <aur:ReturnCertificateCaChain>true</aur:ReturnCertificateCaChain>\n        </aur:request>\n    </aur:RequestCertificate>\n</soapenv:Body>\n</soapenv:Envelope>\\n\"\"\"\n        self.assertEqual(result, self.cahandler._soaprequest_build(pkcs7))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_028_binary_read(self):\n        \"\"\"test read binary file\"\"\"\n        self.assertEqual(\"foo\", self.binary_read(self.logger, \"filename\"))\n\n    @patch(\"builtins.open\", mock_open(read_data=\"foo\"), create=True)\n    def test_029_binary_write(self):\n        \"\"\"test wrote binary file\"\"\"\n        self.assertFalse(self.binary_write(self.logger, \"filename\", \"content\"))\n\n    def test_030_sign(self):\n        \"\"\"test _sign unkown key format\"\"\"\n        key = \"key\"\n        payload = \"foo\"\n        self.assertEqual((None, None), self.cahandler._sign(key, payload))\n\n    @patch(\"cryptography.hazmat.primitives.asymmetric.rsa\")\n    def test_031_sign(self, mock_rsa):\n        \"\"\"test _sign rsa key\"\"\"\n        keyph = b\"Test1234\"\n        with open(self.dir_path + \"/ca/sub-ca-key.pem\", \"rb\") as open_file:\n            key = serialization.load_pem_private_key(\n                open_file.read(), password=keyph, backend=default_backend()\n            )\n        payload = b\"foo\"\n        result = self.cahandler._sign(key, payload)\n        signature = b\"4oTEIybGnmkfnG+Fvf0t8Sx8YHSf55tm3WtcdPagvtNM3vLjsidWKc2yliGYVmDqT9E+/wx3tvsMeDrgRiAzMhbjPYOeKwyx30BZT++4Fw9OkRQyriwyLB3ncFReVF8DyBRj/3S1Ftoy6Msa2CCk59LhYm/ubBQAm88gYiBzCFtVhneNOg5vS2s79UuyLjE2J90Yjs3z7OCckWrZ1UxI3UBoaJAWQg83M6fnF4aMkpnO3Jd6oQ4nq7r4EeVKYYEwrOINKKfh/1ykaCLg2K9OAD2LY1b9LilHTG8lcoUhS+bBMJkESHi508EzFQ4IUdsA42porTkEkdc5g9ZmCm7PPjroSRZGtM00R6aV/4z8Tlp4JBaov9x3fUd5wKjGIP0mdLQamAfxhK/pUqzM/lXtndprV7yh07tzypHa1XNvmTn/di2jNu90cq3eGgi3nBY98u+GcHTFnFH2aW2hk7kxqmxT4ymsZhlviIX8GIT4blE2nJgcl91Ktxm9QataRMjny/uJd//olQAXGMcbDwhNpYBfdJe99XoeuY+xNtJtlQt7IciTmJ3DEcK2kTtsNZ2i/lvn+iYR4iD9fJ/S4FedHqPZi48Q+LSnGC61zD21ZgbT8FrzUTnmmgw9BeDTWezGDGgBdOIuG313waZlvdDahk+6AYz9tOxS+bm9Epcj3NY=\"\n        alg = \"\"\"AlgorithmIdentifier:\\n algorithm=1.2.840.113549.1.1.11\\n parameters=0x0500\\n\"\"\"\n        self.assertEqual(signature, base64.b64encode(result[0]))\n        self.assertEqual(alg, str(result[1]))\n\n    @patch(\"cryptography.hazmat.primitives.asymmetric.rsa\")\n    def test_032_sign(self, mock_rsa):\n        \"\"\"test _sign ecc key\"\"\"\n        ecc_key = b\"-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIGCu1fYGkqMdPtsNH7xVc8QBjCWCkcUTVKX6f8vLhtkvoAoGCCqGSM49\\nAwEHoUQDQgAEan72++swi7J5B1HVYp1CjXPqckkQquiMIQhz5xYesv9f4KK/ouKS\\n1uJ3ZYwPbWUsDd8/03vf9VdlfZzL3W3ZQw==\\n-----END EC PRIVATE KEY-----\"\n        key = serialization.load_pem_private_key(\n            ecc_key, password=None, backend=default_backend()\n        )\n        payload = b\"foo\"\n        result = self.cahandler._sign(key, payload)\n        alg = \"\"\"AlgorithmIdentifier:\\n algorithm=1.2.840.10045.4.3.2\\n\"\"\"\n        self.assertEqual(alg, str(result[1]))\n\n    def test_033_certraw_get(self):\n        \"\"\" test _certraw_get \"\"\" \"\"\n        with open(self.dir_path + \"/ca/sub-ca-client.pem\", \"r\") as fso:\n            pem_data = fso.read()\n        result = \"MIIEGDCCAgCgAwIBAgIJALL8aztMPfV2MA0GCSqGSIb3DQEBCwUAMEgxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xFzAVBgNVBAoMDkFjbWUyQ2VydGlmaWVyMQ8wDQYDVQQDDAZzdWItY2EwHhcNMTkwNjI1MDEyNTAwWhcNMjAwNjI1MDEyNTAwWjBPMQswCQYDVQQGEwJERTEPMA0GA1UEBxMGQmVybGluMRcwFQYDVQQKEw5BY21lMkNlcnRpZmllcjEWMBQGA1UEAwwNY2xpZW50X3N1Yi1jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALvoKKg3ciBVWZtquiWyMogWU6ydEfmLbXktK6T+owxzxHVaoePVGH9DZvTZD2pHS8xJ6fpFr3pZYiuqiUHuxdMpj9gVxik5ivBrSJIkZXLxwvNJWpMa1o1Hxz1By3Hrlm3ebKIzfQPqRRcdjWtJgCFbcTpalwhE1RQFMp4Icb08aAE9uEaZQ4uZ8Ls30J6IHC4PG63lGI1tkAtLIoUWupRAmnWDx0ysXzXeN7m+Lff9ols9MZNgzRMgY/zGUq0LzZfi+L+Iev3sztCdoIOBA/K63jv0hOPyYg331L05XIwbLeUoUG41J4pZzafx6MAFp4Zam1w+aafCzEw7ZPHQvn0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEABPgWo4KAXJNXNfEBbixDuCxtwO1JuphSOTcpIlEp+uNOSDzgNEbrhUXTNM8SPshzFjBpudc29okiyC62CfLD/X+EvIeKo/oa477kN6MuNfqLGZ42a935ES3S00Wy8rbwyIoPCsKWT/6VsHRHUn8XhFNFUBKZ8FGxwXcAVpPanyikURqVH1MgAk62hJQdYjSxdga/GKS1dS39fyxQz7uBPt5WIQZPzL6dr2Yn/4lQUvTUVus2e1cTh3z02yB5EDlEAcMMvMNpfYvNdU5H6QEPwysbkW9E/Ep84aq21zwuPxICh0KdjHWKkHtCqDoEYIADDl1AD5UdJTMQ9LIzUjsBvtB5I6yT7jgsx/iqTDrkJVK/zRf4NeKRa3AW57jsPUIcUstUFnVJbg+MM4fYmapx8Hqm/Aq+II9ip80AM6hXvierTQn4MNQivL0ZJfj0Ro9KEIDAHN3IAfIlFovbkBPLMi9PtfyhuVmXpthE9OaDlgUguWb45LAKwgfu1TFGPPpf5jTw2qVx0F+iCiUwK8ZgnakkXOKE5+KIb8ejL+3pPd5Wt+45w/7gEFOjT6XAzZGnUtcMH/lpxmgbl3/SKkyrW4h7PnF2FEEVC4XnZuQm+ZwD/PpXfmAA52ygKHBzUr9V33CkW0FhvjqkAUya5x9CqWlHoal0RVvFavnw+4ImqbE=\"\n        self.assertEqual(result, self.cahandler._certraw_get(pem_data))\n\n    def test_034_pkcs7_create(self):\n        \"\"\"test pkcs7_create\"\"\"\n        keyph = b\"Test1234\"\n        with open(self.dir_path + \"/ca/csr.der\", \"rb\") as open_file:\n            csr_der = open_file.read()\n        with open(self.dir_path + \"/ca/sub-ca-key.pem\", \"rb\") as open_file:\n            signing_key = serialization.load_pem_private_key(\n                open_file.read(), password=keyph, backend=default_backend()\n            )\n        with open(self.dir_path + \"/ca/sub-ca-cert.pem\", \"rb\") as open_file:\n            signing_cert = x509.load_pem_x509_certificate(\n                open_file.read(), default_backend()\n            )\n\n        decoded_cert = self.cahandler._cert_decode(signing_cert)\n        expected_result = b\"MIIKNwYJKoZIhvcNAQcCoIIKKDCCCiQCAQExDTALBglghkgBZQMEAgEwggKdBgkqhkiG9w0BBwGgggKOBIICijCCAoYwggFuAgEAMBcxFTATBgNVBAMMDGFjbWUtc2guYWNtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMX1XO9sh74B1Vb8IrO4mmrue76dos2Ata2STI+zQjo+ZJVb76pLF6s5SayPtlwZIIetFbeMRhfTatDRn78LTGZBrKKGJbxw2x1oMDQAESqvq5tpbgAxRrbS9V/NDyCDFfjO3YFKsTv2TLY1MDpO7CbypfdsBWImOZKe1pNfyXGDCwQzmVo8Orf69vvVA8b+FFJeg2rxWEvTJdnYpOb5VhblZ8voexo/6pxgZWm6iGJ77pytfDQDHBT29/rdOMXN19nZYBEO9iK1P0xoRJfZ/LSGQSTo0EgFdtIVWgp1ebYelUyF5in2pstPKpdUSV0RIZFalBO88PZM5Q2v+uaTfOsCAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIMYWNtZS1zaC5hY21lMA0GCSqGSIb3DQEBCwUAA4IBAQCEmZyZpsuSQAjGirts9HgmIZZT1LMenGjwqcUILEAdP0TCrczTftT59ZIWfIvNjx7APGTdhIjYHLv46IJMZA3BAGI57vBmQUJg0KCOlKub9KIsx4ydjMXbNkIZBVEFo37IaXvXyVv32gQVvkxl7ZCrpNfyntT1+6Sb4T7uaho3HBHZ+Hharwlwudq6N+WC8XoLROWoD0mTVg5c/kG9nT+17LKs8BMvfBlReYRUEJZsT5a9xEwhDqODyL7oibucyOH7kU8/G2qplh5YKKhM32CkXXk5DAejiBI1wnlOcR5RElt7QnjzJEazNe+Q7DcQjXp0cHT1pjVFDresthfd6StPoIIFIjCCBR4wggMGoAMCAQICCHBVGGSyAlB6MA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNjA5MTcxODAwWhcNMzAwNjA5MTcxNzAwWjARMQ8wDQYDVQQDEwZzdWItY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDtsQlyE4FXBSbqoeYm3QVMGjSYCN5QZhtmi47yGgV2x96HB+lrFztXeYt+z3qQK5k6Rn3fhMNxb1Jsoj8xTt1iUsIJNPesqC1UB8AHMcrstXQV3phhQZt7+aH0yvjMiDcSTz5EVmyS4UhE6H8wqP72xZAaiJBaGq4fLhMH8c4aQ6t3Fo0TmiYR4U/uhrGwzBqLi82vSdR1bOBZ+X5JhcQfYO7LeWdfU1SCgorDz+FUDZm4WlrhyTJGlw5GlQFHMOkEMqrsH3Ze/I53YdeA/LRbqC2XEcU/3H0D5qoXI45JE3pTJP+Tn2JZPtcI6ABE6Fw8xh05F0v85BjHWXmRbLVwBYctEx5UjDuUU7isEl8SDm7yijNlnTVaZ2Dg+V2mZ7xSceX0Ltdx4ja6a0CkALLIoSqs/YgnidMbsLiMnZK5o10lNCrcs0mVwYGmjEnkWMfnRoVX79X+lPjEIwavkBG5Lmn3BbN057kXG21gOB/k+HCSt5K4PZvbNT9rUwBWLjQwEQ3+iIDz8nkoJXDKSj6oO7mVkeXv9MEI9vVy1IK5BaCD7CxDC+mikzDnYglHHQHZ3ppMHAeySLYfhwHkozaVtZUW9eEDcW3+dqsTdF/B7AzWJoPvq8cTjsBDM2LqOwodQpcyNERmkRx25Fspo/naMl71cJ1eGWcEV16XiZoZkwIDAQABo3kwdzASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSDJ855iatD1k7LCUzmM5yhe4IzeDAfBgNVHSMEGDAWgBS/zoiPYe7Wln8qrB80MMIqtzQbzjAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBCwUAA4ICAQCTMEN9/rS9sjvrXj2w2W+WYgEngCOhZh1i7U6cd2HgwV0dTRbTBkdY2IljuTHOgQJiwtij3r17flTO0VnkFD5TCn3G8V+V3a4TFsgtB0rxkLYNPxbXOnaPDI98DiK5pbJCTw1/bOFU9Hq7Gm0XWdg45HMrm+T4qTHCXD0eyKZ3yyS3Ctf0MawB2bXbHlLjsr13pQKD1kzy5OLjMRMxpJUw4aows1XN/rESTsFfUEKKTl97Qeb4owMwveo60Y/dFDQ2QbfSCbtLASGK6P2vTgKsRW0F3LK+q1GYL5LVoIIaiTmov4onUwgNEzOEqiVLmqJOILiZjExnPJiWfhH5lfCTyf/Dmj9ilNlXDA86jePynmbe/rXxuxgd4epdw+zP6vKpEmGKNp80ONORAfylWKIYcPOUXCcN86p84hbk5k00qruMzi5RhcEq4u1YB9yX5oBlpo0OgfMD91dIysnRWyWiDODyz0WXgh33sSdyLtmte+LGkocQcAbHwlWofvY+jyfD78fC8z1vlnsluejaRRWpsLCSSqmn7wTLmT4wkfm7qwzyYfWOyKz2TQ7IJgXFMwfQQsQdUJY+H3ZInrhyTOZuo2jnlJZxAqa5MrrcoeZRGNAVcOUTvr/UqrSP+nGxa3JTHG9UqReVtLJRF98UxtNgbwZQjiq2Zap6f40nZgGfbDGCAkcwggJDAgEBMB4wEjEQMA4GA1UEAxMHcm9vdC1jYQIIcFUYZLICUHowCwYJYIZIAWUDBAIBMA0GCSqGSIb3DQEBAQUABIICAFavMaudlAWiY6+4IspRR6RplBde2LeAB/F0ZDrq8c+IxTJhfiU2mayw6ToQUBs0KngLP1TSsCVUZDOr6Q+uQktvsP2K7rMkackVcXr43DI+QxeVZtGBYhWSdFC5KofW5Bx0u38b8uIQ1sa2FulZtaiEDJ+aXVDZPRDdxxWQ6zXq0zyEblVGuJwPhGGdHeOdG16yma7gY742g5dpRodi4FJ6oblHZ1LDTuWLMcQnyd3935c8vzKjf0IWrBWW0ShR6UAFnbVSbK2cyqq8T/aVdl0Wc8Ld76KsJgO8i4w5ooLBn7ws/YnZhohVx0mhrmUuItiLSkx4veInVBZfMTf92vL9iUWUZDFycTMIwDZAax1DTpbSVNm0isJkrH9Vj5TohEfimcGim7cyHydefq/ldjHRvN2b5VWp3o3S+6TYUriPsQgmk+oW8Ew+hv2wmXkP+Kg8gA72D80+g9BgptrcdvNvUYBx5o8WA1Nhqsy2eZyFLz5uzYvO5i4aI9e1wf8Pdykdge4803YZkktA/ORXct4CYINCDWaa5FT4NAS9TOOONZsGxugKWtArZCAiBCnGEjD+P5rJp/CechMNZmNQvnd7s/JtRrRKdKMxqViXT8Xqk2GQdWmxaHYU/Xh62TWhfD4Vyac2kDkd2QntHnACexdmoLyk6H5GP3mC9+9ym2Qx\"\n        (_error, result) = self.cahandler._pkcs7_create(\n            decoded_cert, csr_der, signing_key\n        )\n        self.assertEqual(expected_result, base64.b64encode(result))\n\n    @patch(\"requests.post\")\n    def test_035_soaprequest_send(self, mock_post):\n        \"\"\"soaprequest_send() - request exception\"\"\"\n        mock_post.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Connection error\", None), self.cahandler._soaprequest_send(\"payload\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:SOAP request to CA failed: exc_api_post\", lcm.output\n        )\n\n    @patch(\"xmltodict.parse\")\n    @patch(\"requests.post\")\n    def test_036_soaprequest_send(self, mock_post, mock_xml_parse):\n        \"\"\"soaprequest_send() - 200 xml-parsing error\"\"\"\n        mock_post.return_value = Mock(status_code=200)\n        mock_xml_parse.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Parsing error\", None), self.cahandler._soaprequest_send(\"payload\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:XML parsing error in SOAP response from CA.\",\n            lcm.output,\n        )\n\n    @patch(\"xmltodict.parse\")\n    @patch(\"requests.post\")\n    def test_037_soaprequest_send(self, mock_post, mock_xml_parse):\n        \"\"\"soaprequest_send() - 200 xml-parsing successful\"\"\"\n        mock_post.return_value = Mock(status_code=200)\n        mock_xml_parse.return_value = {\n            \"s:Envelope\": {\n                \"s:Body\": {\n                    \"RequestCertificateResponse\": {\n                        \"RequestCertificateResult\": {\"IssuedCertificate\": \"foo\"}\n                    }\n                }\n            }\n        }\n        self.assertEqual((None, \"foo\"), self.cahandler._soaprequest_send(\"payload\"))\n\n    @patch(\"xmltodict.parse\")\n    @patch(\"requests.post\")\n    def test_038_soaprequest_send(self, mock_post, mock_xml_parse):\n        \"\"\"soaprequest_send() - 400 xml-parsing error\"\"\"\n        mock_post.return_value = Mock(status_code=400)\n        mock_xml_parse.return_value = {\"foo\": \"bar\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Server error\", None), self.cahandler._soaprequest_send(\"payload\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:CA server returned HTTP error status: 400\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:Unknown error while parsing SOAP response from CA.\",\n            lcm.output,\n        )\n\n    @patch(\"xmltodict.parse\")\n    @patch(\"requests.post\")\n    def test_039_soaprequest_send(self, mock_post, mock_xml_parse):\n        \"\"\"soaprequest_send() - 400 xml-parsing successful\"\"\"\n        mock_post.return_value = Mock(status_code=400)\n        mock_xml_parse.return_value = {\n            \"s:Envelope\": {\n                \"s:Body\": {\n                    \"s:Fault\": {\"faultcode\": \"faultcode\", \"faultstring\": \"faultstring\"}\n                }\n            }\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Server error\", None), self.cahandler._soaprequest_send(\"payload\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:CA server returned HTTP error status: 400\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:SOAP response contains faultcode: faultcode\",\n            lcm.output,\n        )\n        self.assertIn(\n            \"ERROR:test_a2c:SOAP response contains faultstring: faultstring\",\n            lcm.output,\n        )\n\n    def test_040_get_certificates(self):\n        \"\"\"test pkcs7_create\"\"\"\n        with open(self.dir_path + \"/ca/certs_der.p7b\", \"rb\") as open_file:\n            pkcs7_bundle = open_file.read()\n        result = [\n            \"-----BEGIN CERTIFICATE-----\\nMIIFTzCCAzegAwIBAgIIAzHyhSyrXfMwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MTM1\\nNDAwWhcNMzAwNTI2MjM1OTAwWjAqMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEP\\nMA0GA1UEAxMGc3ViLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\\nxXHaGZsolXe+PBdUryngHP9VbBC1mehqeTtYI+hqsqGNH7q9a7bSrxMwFuF1kYL8\\njqqxkJdtl0L94xcxJg/ZdMx7Nt0vGI+BaAuTpEpUEHeN4tqS6NhB/m/0LGkAELc/\\nqkzmoO4B1FDwEEj/3IXtZcupqG80oDt7jWSGXdtF7NTjzcumznMeRXidCdhxRxT/\\n/WrsChaytXo0xWZ56oeNwd6x6Dr8/39PBOWtj4fldyDcg+Q+alci2tx9pxmu2bCV\\nXcB9ftCLKhDk2WEHE88bgKSp7fV2RCmq9po+Tx8JJ7qecLunUsK/F0XN4kpoQLm9\\nhcymqchnMSncSiyin1dQHGHWgXDtBDdq6A2Z6rx26Qk5H9HTYvcNSe1YwFEDoGLB\\nZQjbCPWiaqoaH4agBQTclPvrrSCRaVmhUSO+pBtSXDkmN4t3MDZxfgRkp8ixwkB1\\n5Y5f0LTpCyAJsdQDw8+Ea0aDqO30eskh4CErnm9+Fejd9Ew2cwpdwfBXzVSbYilM\\nGueQihZHvJmVRxAwU69aO2Qs8B0tQ60CfWKVlmWPiakrvYYlPp0FBsM61G6LZEN8\\nhH2CKnS8hHv5IWEXZvp0Pk8V3P5h6bWN0Tl+x/V1Prt7Wp8NoiPETE8XyDDxe6dm\\nKxztWBH/mTsJyMGb6ZiUoXdPU9TFUKqHxTRLHaxfsPsCAwEAAaN4MHYwEgYDVR0T\\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUv96OjgYiIqutQ8jd1E+oq0hBPtUwDgYD\\nVR0PAQH/BAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYP\\neGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQBbHLEVyg4f9uEujroc\\n31UVyDRLMdPgEPLjOenSBCBmH0N81whDmxNI/7JAAB6J14WMX8OLF0HkZnb7G77W\\nvDhy1aFvQFbXHBz3/zUO9Mw9J4L2XEW6ond3Nsh1m2oXeBde3R3ANxuIzHqZDlP9\\n6YrRcHjnf4+1/5AKDJAvJD+gFb5YnYUKH2iSvHUvG17xcZx98Rf2eo8LealG4JqH\\nJh4sKRy0VjDQD7jXSCbweTHEb8wz+6OfNGrIo+BhTFP5vPcwE4nlJwYBoaOJ5cVa\\n7gdQJ7WkLSxvwHxuxzvSVK73u3jl3I9SqTrbMLG/jeJyV0P8EvdljOaGnCtQVRwC\\nzM4ptXUvKhKOHy7/nyTF/Bc35ZwwL/2xWvNK1+NibgE/6CFxupwWpdmxQbVVuoQ3\\n2tUil9ty0yC6m5GKE8+t1lrZuxyA+b/TBnYNO5xo8UEMbkpxaNYSwmw+f/loxXP/\\nM7sIBcLvy2ugHEBxwd9o/kLXeXT2DaRvxPjp4yk8MpJRpNmz3aB5HJwaUnaRLVo5\\nZ3XWWXmjMGZ6/m0AAoDbDz/pXtOoJZT8BJdD1DuDdszVsQnLVn4B/LtIXL6FbXsF\\nzfv6ERP9a5gpKUZ+4NjgrnlGtdccNZpwyWF0IXcvaq3b8hXIRO4hMjzHeHfzJN4t\\njX1vlY35Ofonc4+6dRVamBiF9A==\\n-----END CERTIFICATE-----\\n\",\n            \"-----BEGIN CERTIFICATE-----\\nMIIFcDCCA1igAwIBAgIIevLTTxOMoZgwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE\\nCxMOYWNtZTJjZXJ0aWZpZXIxEDAOBgNVBAMTB3Jvb3QtY2EwHhcNMjAwNTI3MDAw\\nMDAwWhcNMzAwNTI2MjM1OTU5WjArMRcwFQYDVQQLEw5hY21lMmNlcnRpZmllcjEQ\\nMA4GA1UEAxMHcm9vdC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\\nAJy4UZHdZgYt64k/rFamoC676tYvtabeuiqVw1c6oVZI897cFLG6BYwyr2Eaj7tF\\nrqTJDeMN4vZSudLsmLDq6m8KwX/riPzUTIlcjM5aIMANZr9rLEs3NWtcivolB5aQ\\n1slhdVitUPLuxsFnYeQTyxFyP7lng9M/Z403KLG8phdmKjM0vJkaj4OuKOXf3UsW\\nqWQYyRl/ms07xVj02uq08LkoeO+jtQisvyVXURdaCceZtyK/ZBQ7NFCsbK112cVR\\n1e2aJol7NJAA6Wm6iBzAdkAA2l3kh40SLoEbaiaVMixLN2vilIZOOAoDXX4+T6ir\\n+KnDVSJ2yu5c/OJMwuXwHrh7Lgg1vsFR5TNehknhjUuWOUO+0TkKPg2A7KTg72OZ\\n2mOcLZIbxzr1P5RRvdmLQLPrTF2EJvpQPNmbXqN3ZVWEvfHTjkkTFY/dsOTvFTgS\\nri15zYKch8votcU7z+BQhgmMtwO2JhPMmZ6ABd9skI7ijWpwOltAhxtdoBO6T6CB\\nCrE2yXc6V/PyyAKcFglNmIght5oXsnE+ub/dtx8f9Iea/xNPdo5aGy8fdaitolDK\\n16kd3Kb7OE4HMHIwOxxF1BEAqerxxhbLMRBr8hRSZI5cvLzWLvpAQ5zuhjD6V3b9\\nBYFd4ujAu3zl3mbzdbYjFoGOX6aBZaGDxlc4O2W7HxntAgMBAAGjgZcwgZQwDwYD\\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDGVvuTFYZtEAkz3af9wRKDDvAswwHwYD\\nVR0jBBgwFoAUDGVvuTFYZtEAkz3af9wRKDDvAswwDgYDVR0PAQH/BAQDAgGGMBEG\\nCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRl\\nMA0GCSqGSIb3DQEBCwUAA4ICAQAjko7dX+iCgT+m3Iy1Vg6j7MRevPAzq1lqHRRN\\nNdt2ct530pIut7Fv5V2xYk35ka+i/G+XyOvTXa9vAUKiBtiRnUPsXu4UcS7CcrCX\\nEzHx4eOtHnp5wDhO0Fx5/OUZTaP+L7Pd1GD/j953ibx5bMa/M9Rj+S486nst57tu\\nDRmEAavFDiMd6L3jH4YSckjmIH2uSeDIaRa9k6ag077XmWhvVYQ9tuR7RGbSuuV3\\nFc6pqcFbbWpoLhNRcFc+hbUKOsKl2cP+QEKP/H2s3WMllqgAKKZeO+1KOsGo1CDs\\n475bIXyCBpFbH2HOPatmu3yZRQ9fj9ta9EW46n33DFRNLinFWa4WJs4yLVP1juge\\n2TCOyA1t61iy++RRXSG3e7NFYrEZuCht1EdDAdzIUY89m9NCPwoDYS4CahgnfkkO\\n7YQe6f6yqK6isyf8ZFcp1uF58eERDiF/FDqS8nLmCdURuI56DDoNvDpig5J/9RNW\\nG8vEvt2p7QrjeZ3EAatx5JuYty/NKTHZwJWk51CgzEgzDwzE2JIiqeldtL5d0Sl6\\neVuv0G04BEyuXxEWpgVVzBS4qEFIBSnTJzgu1PXmId3yLvg2Nr8NKvwyZmN5xKFp\\n0A9BWo15zW1PXDaD+l39oTYD7agjXkzTAjYIcfNJ7ATIYFD0xAvNAOf70s7aNupF\\nfvkG2Q==\\n-----END CERTIFICATE-----\\n\",\n        ]\n        self.assertEqual(result, self.cahandler._get_certificate(pkcs7_bundle))\n\n    def test_041_pkcs7_signing_config_verify(self):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        self.cahandler.signing_script_dic = {}\n        self.assertEqual(\n            \"signing config incomplete: option signing_script is missing\",\n            self.cahandler._pkcs7_signing_config_verify(),\n        )\n\n    def test_042_pkcs7_signing_config_verify(self):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        self.cahandler.signing_script_dic = {\"signing_script\": \"signing_script\"}\n        self.assertEqual(\n            \"signing config incomplete: option signing_alias is missing\",\n            self.cahandler._pkcs7_signing_config_verify(),\n        )\n\n    def test_043_pkcs7_signing_config_verify(self):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_alias\": \"signing_alias\",\n        }\n        self.assertEqual(\n            \"signing config incomplete: option signing_csr_path is missing\",\n            self.cahandler._pkcs7_signing_config_verify(),\n        )\n\n    @patch(\"os.path.isdir\")\n    def test_044_pkcs7_signing_config_verify(self, mock_path):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        mock_path.return_value = False\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_alias\": \"signing_alias\",\n            \"signing_csr_path\": \"signing_csr_path\",\n        }\n        self.assertEqual(\n            \"signing_csr_path signing_csr_path does not exist or is not a directory\",\n            self.cahandler._pkcs7_signing_config_verify(),\n        )\n\n    @patch(\"os.path.isdir\")\n    def test_045_pkcs7_signing_config_verify(self, mock_path):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        mock_path.return_value = True\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_alias\": \"signing_alias\",\n            \"signing_csr_path\": \"signing_csr_path\",\n        }\n        self.assertEqual(\n            \"signing config incomplete: option signing_config_variant is missing\",\n            self.cahandler._pkcs7_signing_config_verify(),\n        )\n\n    @patch(\"os.path.isdir\")\n    def test_046_pkcs7_signing_config_verify(self, mock_path):\n        \"\"\"test _pkcs7_signing_config_verify()\"\"\"\n        mock_path.return_value = True\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_alias\": \"signing_alias\",\n            \"signing_csr_path\": \"signing_csr_path\",\n            \"signing_config_variant\": \"signing_config_variant\",\n        }\n        self.assertEqual(None, self.cahandler._pkcs7_signing_config_verify())\n\n    def test_047_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {}\n        self.assertEqual(\n            [], self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\")\n        )\n\n    def test_048_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {}\n        self.assertEqual(\n            [], self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\")\n        )\n\n    def test_049_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\"signing_user\": \"signing_user\"}\n        self.assertEqual(\n            [], self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\")\n        )\n\n    def test_050_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_user\": \"signing_user\",\n            \"signing_script\": \"signing_script\",\n        }\n        self.assertEqual(\n            [\"sudo\", \"signing_user\", \"signing_script\", \"csr_unsigned\", \"csr_signed\"],\n            self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\"),\n        )\n\n    def test_051_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_user\": \"signing_user\",\n            \"signing_script\": \"signing_script\",\n            \"signing_interpreter\": \"signing_interpreter\",\n        }\n        self.assertEqual(\n            [\n                \"sudo\",\n                \"signing_user\",\n                \"signing_interpreter\",\n                \"signing_script\",\n                \"csr_unsigned\",\n                \"csr_signed\",\n            ],\n            self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\"),\n        )\n\n    def test_052_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_interpreter\": \"signing_interpreter\",\n        }\n        self.assertEqual(\n            [\"signing_interpreter\", \"signing_script\", \"csr_unsigned\", \"csr_signed\"],\n            self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\"),\n        )\n\n    def test_053_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_interpreter\": \"signing_interpreter\",\n            \"signing_alias\": \"signing_alias\",\n        }\n        self.assertEqual(\n            [\"signing_interpreter\", \"signing_script\", \"csr_unsigned\", \"csr_signed\"],\n            self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\"),\n        )\n\n    def test_054_signing_command_build(self):\n        \"\"\"test _signing_command_build()\"\"\"\n        self.cahandler.signing_script_dic = {\n            \"signing_script\": \"signing_script\",\n            \"signing_interpreter\": \"signing_interpreter\",\n            \"signing_alias\": \"signing_alias\",\n            \"signing_config_variant\": \"signing_config_variant\",\n        }\n        self.assertEqual(\n            [\n                \"signing_interpreter\",\n                \"signing_script\",\n                \"csr_unsigned\",\n                \"csr_signed\",\n                \"signing_alias\",\n                \"signing_config_variant\",\n            ],\n            self.cahandler._signing_command_build(\"csr_unsigned\", \"csr_signed\"),\n        )\n\n    @patch(\"os.remove\")\n    @patch(\"os.path.isfile\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_read\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_write\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._signing_command_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.generate_random_string\")\n    @patch(\n        \"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_signing_config_verify\"\n    )\n    def test_055_pkcs7_sign_external(\n        self,\n        mock_vrf,\n        mock_rand,\n        mock_build,\n        mock_write,\n        mock_call,\n        mock_read,\n        mock_file,\n        mock_rm,\n    ):\n        \"\"\"test _pkcs7_sign_external()\"\"\"\n        mock_vrf.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"Config incomplete\", None), self.cahandler._pkcs7_sign_external(\"csr\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:External signing configuration is incomplete: True\",\n            lcm.output,\n        )\n        self.assertFalse(mock_rand.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_write.called)\n        self.assertFalse(mock_call.called)\n        self.assertFalse(mock_read.called)\n        self.assertFalse(mock_file.called)\n        self.assertFalse(mock_rm.called)\n\n    @patch(\"os.remove\")\n    @patch(\"os.path.isfile\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_read\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_write\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._signing_command_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.generate_random_string\")\n    @patch(\n        \"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_signing_config_verify\"\n    )\n    def test_056_pkcs7_sign_external(\n        self,\n        mock_vrf,\n        mock_rand,\n        mock_build,\n        mock_write,\n        mock_call,\n        mock_read,\n        mock_file,\n        mock_rm,\n    ):\n        \"\"\"test _pkcs7_sign_external() all good\"\"\"\n        mock_vrf.return_value = False\n        mock_read.return_value = \"foo\"\n        mock_call.return_value = None\n        mock_file.return_value = True\n        self.cahandler.signing_script_dic = {\"signing_csr_path\": \"signing_csr_path\"}\n        # with self.assertLogs('test_a2c', level='INFO') as lcm:\n        self.assertEqual((None, \"foo\"), self.cahandler._pkcs7_sign_external(\"csr\"))\n        # self.assertIn('ERROR:test_a2c:CAhandler._pkcs7_sign_external(): config incomplete: True', lcm.output)\n        self.assertTrue(mock_rand.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_write.called)\n        self.assertTrue(mock_call.called)\n        self.assertTrue(mock_read.called)\n        self.assertTrue(mock_file.called)\n        self.assertTrue(mock_rm.called)\n\n    @patch(\"os.remove\")\n    @patch(\"os.path.isfile\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_read\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_write\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._signing_command_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.generate_random_string\")\n    @patch(\n        \"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_signing_config_verify\"\n    )\n    def test_057_pkcs7_sign_external(\n        self,\n        mock_vrf,\n        mock_rand,\n        mock_build,\n        mock_write,\n        mock_call,\n        mock_read,\n        mock_file,\n        mock_rm,\n    ):\n        \"\"\"test _pkcs7_sign_external() no delete\"\"\"\n        mock_vrf.return_value = False\n        mock_read.return_value = \"foo\"\n        mock_call.return_value = None\n        mock_file.return_value = False\n        self.cahandler.signing_script_dic = {\"signing_csr_path\": \"signing_csr_path\"}\n        self.assertEqual((None, \"foo\"), self.cahandler._pkcs7_sign_external(\"csr\"))\n        self.assertTrue(mock_rand.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_write.called)\n        self.assertTrue(mock_call.called)\n        self.assertTrue(mock_read.called)\n        self.assertTrue(mock_file.called)\n        self.assertFalse(mock_rm.called)\n\n    @patch(\"os.remove\")\n    @patch(\"os.path.isfile\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_read\")\n    @patch(\"subprocess.call\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.binary_write\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._signing_command_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.generate_random_string\")\n    @patch(\n        \"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_signing_config_verify\"\n    )\n    def test_058_pkcs7_sign_external(\n        self,\n        mock_vrf,\n        mock_rand,\n        mock_build,\n        mock_write,\n        mock_call,\n        mock_read,\n        mock_file,\n        mock_rm,\n    ):\n        \"\"\"test _pkcs7_sign_external() subprocess call returns something\"\"\"\n        mock_vrf.return_value = False\n        mock_read.return_value = \"foo\"\n        mock_call.return_value = 1\n        mock_file.return_value = True\n        self.cahandler.signing_script_dic = {\"signing_csr_path\": \"signing_csr_path\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((1, None), self.cahandler._pkcs7_sign_external(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Certificate enrollment aborted: 1\",\n            lcm.output,\n        )\n        self.assertTrue(mock_rand.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_write.called)\n        self.assertTrue(mock_call.called)\n        self.assertFalse(mock_read.called)\n        self.assertTrue(mock_file.called)\n        self.assertTrue(mock_rm.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._certraw_get\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._get_certificate\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_send\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_create\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._cert_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_sign_external\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_url_recode\")\n    def test_059_enroll(\n        self,\n        mock_recode,\n        mock_decode,\n        mock_sigext,\n        mock_cert_decode,\n        mock_pkcs7_cr,\n        mock_encode,\n        mock_sbuild,\n        mock_ssend,\n        mock_cert_get,\n        mock_cert_raw,\n    ):\n        \"\"\"test enroll() external signature script returning an error\"\"\"\n        self.cahandler.signing_script_dic = {\"foo\": \"bar\"}\n        mock_sigext.return_value = (\"error\", None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler.enroll() aborted with error: error\", lcm.output\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_decode.called)\n        self.assertTrue(mock_sigext.called)\n        self.assertFalse(mock_cert_decode.called)\n        self.assertFalse(mock_pkcs7_cr.called)\n        self.assertFalse(mock_encode.called)\n        self.assertFalse(mock_sbuild.called)\n        self.assertFalse(mock_ssend.called)\n        self.assertFalse(mock_cert_get.called)\n        self.assertFalse(mock_cert_raw.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._certraw_get\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._get_certificate\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_send\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_create\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._cert_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_sign_external\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_url_recode\")\n    def test_060_enroll(\n        self,\n        mock_recode,\n        mock_decode,\n        mock_sigext,\n        mock_cert_decode,\n        mock_pkcs7_cr,\n        mock_encode,\n        mock_sbuild,\n        mock_ssend,\n        mock_cert_get,\n        mock_cert_raw,\n    ):\n        \"\"\"test enroll() internal signer returns error\"\"\"\n        self.cahandler.signing_script_dic = {}\n        mock_pkcs7_cr.return_value = (\"error\", None)\n        mock_cert_decode.return_value = \"decoded_cert\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler.enroll() aborted with error: error\", lcm.output\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_decode.called)\n        self.assertFalse(mock_sigext.called)\n        self.assertTrue(mock_cert_decode.called)\n        self.assertTrue(mock_pkcs7_cr.called)\n        self.assertFalse(mock_encode.called)\n        self.assertFalse(mock_sbuild.called)\n        self.assertFalse(mock_ssend.called)\n        self.assertFalse(mock_cert_get.called)\n        self.assertFalse(mock_cert_raw.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._certraw_get\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._get_certificate\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_send\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_create\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._cert_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_sign_external\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_url_recode\")\n    def test_061_enroll(\n        self,\n        mock_recode,\n        mock_decode,\n        mock_sigext,\n        mock_cert_decode,\n        mock_pkcs7_cr,\n        mock_encode,\n        mock_sbuild,\n        mock_ssend,\n        mock_cert_get,\n        mock_cert_raw,\n    ):\n        \"\"\"test enroll() - soap_request_send returns error\"\"\"\n        self.cahandler.signing_script_dic = {}\n        mock_cert_decode.return_value = \"decoded_cert\"\n        mock_pkcs7_cr.return_value = (None, \"pkcs_7\")\n        mock_ssend.return_value = (\"error\", None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:SOAP request to CA failed: error\",\n            lcm.output,\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_decode.called)\n        self.assertFalse(mock_sigext.called)\n        self.assertTrue(mock_cert_decode.called)\n        self.assertTrue(mock_pkcs7_cr.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_sbuild.called)\n        self.assertTrue(mock_ssend.called)\n        self.assertFalse(mock_cert_get.called)\n        self.assertFalse(mock_cert_raw.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._certraw_get\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._get_certificate\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_send\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_create\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._cert_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_sign_external\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_url_recode\")\n    def test_062_enroll(\n        self,\n        mock_recode,\n        mock_decode,\n        mock_sigext,\n        mock_cert_decode,\n        mock_pkcs7_cr,\n        mock_encode,\n        mock_sbuild,\n        mock_ssend,\n        mock_cert_get,\n        mock_cert_raw,\n    ):\n        \"\"\"test enroll() - soap_request_send returns no error but no bundle\"\"\"\n        self.cahandler.signing_script_dic = {}\n        mock_cert_decode.return_value = \"decoded_cert\"\n        mock_pkcs7_cr.return_value = (None, \"pkcs_7\")\n        mock_ssend.return_value = (None, None)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((None, None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:SOAP request to CA did not return a certificate bundle.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_decode.called)\n        self.assertFalse(mock_sigext.called)\n        self.assertTrue(mock_cert_decode.called)\n        self.assertTrue(mock_pkcs7_cr.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_sbuild.called)\n        self.assertTrue(mock_ssend.called)\n        self.assertFalse(mock_cert_get.called)\n        self.assertFalse(mock_cert_raw.called)\n\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._certraw_get\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._get_certificate\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_send\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._soaprequest_build\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_create\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._cert_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.CAhandler._pkcs7_sign_external\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.pkcs7_soap_ca_handler.b64_url_recode\")\n    def test_063_enroll(\n        self,\n        mock_recode,\n        mock_decode,\n        mock_sigext,\n        mock_cert_decode,\n        mock_pkcs7_cr,\n        mock_encode,\n        mock_sbuild,\n        mock_ssend,\n        mock_cert_get,\n        mock_cert_raw,\n    ):\n        \"\"\"test enroll() - soap_request_send returns no error but no bundle\"\"\"\n        self.cahandler.signing_script_dic = {}\n        mock_cert_decode.return_value = \"decoded_cert\"\n        mock_pkcs7_cr.return_value = (None, \"pkcs_7\")\n        mock_ssend.return_value = (None, \"pkcs7_bundle\")\n        mock_cert_get.return_value = [\"cert_1\", \"cert_2\"]\n        mock_cert_raw.return_value = \"cert_raw\"\n        self.assertEqual(\n            (None, \"cert_1cert_2\", \"cert_raw\", None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_recode.called)\n        self.assertTrue(mock_decode.called)\n        self.assertFalse(mock_sigext.called)\n        self.assertTrue(mock_cert_decode.called)\n        self.assertTrue(mock_pkcs7_cr.called)\n        self.assertTrue(mock_encode.called)\n        self.assertTrue(mock_sbuild.called)\n        self.assertTrue(mock_ssend.called)\n        self.assertTrue(mock_cert_get.called)\n        self.assertTrue(mock_cert_raw.called)\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_renewalinfo.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\" unittest for renewalinfo.py \"\"\"\nimport unittest\nfrom unittest.mock import MagicMock, patch\nimport os\nimport sys\n\n# Add the parent directory to sys.path so we can import acme_srv\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nfrom acme_srv.renewalinfo import Renewalinfo, RenewalinfoConfig, RenewalinfoRepository\n\n\nclass TestRenewalinfoConfig(unittest.TestCase):\n    def test_default_values(self):\n        config = RenewalinfoConfig()\n        self.assertFalse(config.renewal_force)\n        self.assertEqual(config.renewalthreshold_pctg, 85.0)\n        self.assertEqual(config.retry_after_timeout, 86400)\n\n\nclass TestRenewalinfoRepository(unittest.TestCase):\n    def setUp(self):\n        self.mock_dbstore = MagicMock()\n        self.logger = MagicMock()\n        self.repo = RenewalinfoRepository(self.mock_dbstore, self.logger)\n\n    def test_get_certificate_by_certid_success(self):\n        self.mock_dbstore.certificate_lookup.return_value = {\"foo\": \"bar\"}\n        result = self.repo.get_certificate_by_certid(\"abc\")\n        self.assertEqual(result, {\"foo\": \"bar\"})\n\n    def test_get_certificate_by_certid_exception(self):\n        self.mock_dbstore.certificate_lookup.side_effect = Exception(\"fail\")\n        result = self.repo.get_certificate_by_certid(\"abc\")\n        self.assertIsNone(result)\n        self.logger.critical.assert_called()\n\n    def test_get_certificates_by_serial_success(self):\n        self.mock_dbstore.certificates_search.return_value = [{\"foo\": \"bar\"}]\n        result = self.repo.get_certificates_by_serial(\"serial\")\n        self.assertEqual(result, [{\"foo\": \"bar\"}])\n\n    def test_get_certificates_by_serial_exception(self):\n        self.mock_dbstore.certificates_search.side_effect = Exception(\"fail\")\n        result = self.repo.get_certificates_by_serial(\"serial\")\n        self.assertEqual(result, [])\n        self.logger.critical.assert_called()\n\n    def test_add_certificate(self):\n        self.repo.add_certificate({\"foo\": \"bar\"})\n        self.mock_dbstore.certificate_add.assert_called_with({\"foo\": \"bar\"})\n\n    def test_get_housekeeping_param(self):\n        self.repo.get_housekeeping_param(\"name\")\n        self.mock_dbstore.hkparameter_get.assert_called_with(\"name\")\n\n    def test_add_housekeeping_param(self):\n        self.repo.add_housekeeping_param({\"foo\": \"bar\"})\n        self.mock_dbstore.hkparameter_add.assert_called_with({\"foo\": \"bar\"})\n\n\nclass TestRenewalinfo(unittest.TestCase):\n    def setUp(self):\n        self.mock_dbstore = MagicMock()\n        self.mock_logger = MagicMock()\n        self.mock_message = MagicMock()\n        self.mock_repository = MagicMock()\n        self.mock_config = RenewalinfoConfig(\n            renewal_force=True, renewalthreshold_pctg=90.0, retry_after_timeout=1234\n        )\n        patcher_db = patch(\n            \"acme_srv.renewalinfo.DBstore\", return_value=self.mock_dbstore\n        )\n        patcher_msg = patch(\n            \"acme_srv.renewalinfo.Message\", return_value=self.mock_message\n        )\n        patcher_err = patch(\n            \"acme_srv.renewalinfo.error_dic_get\", return_value={\"malformed\": \"malf\"}\n        )\n        patcher_repo = patch(\n            \"acme_srv.renewalinfo.RenewalinfoRepository\",\n            return_value=self.mock_repository,\n        )\n        patcher_certid_hex = patch(\n            \"acme_srv.renewalinfo.certid_hex_get\", return_value=(None, \"hex\")\n        )\n        self.addCleanup(patcher_db.stop)\n        self.addCleanup(patcher_msg.stop)\n        self.addCleanup(patcher_err.stop)\n        self.addCleanup(patcher_repo.stop)\n        self.addCleanup(patcher_certid_hex.stop)\n        patcher_db.start()\n        patcher_msg.start()\n        patcher_err.start()\n        patcher_repo.start()\n        patcher_certid_hex.start()\n        self.renewalinfo = Renewalinfo(\n            debug=True, srv_name=\"srv\", logger=self.mock_logger\n        )\n        self.renewalinfo.config = self.mock_config\n        self.renewalinfo.repository = self.mock_repository\n\n    def test_001_get_housekeeping_triggers_update(self):\n        self.mock_repository.get_housekeeping_param.return_value = False\n        self.mock_repository.add_housekeeping_param.return_value = True\n        self.mock_repository.get_certificate_by_certid.return_value = {\n            \"expire_uts\": 100000,\n            \"issue_uts\": 90000,\n        }\n        self.mock_repository.get_certificates_by_serial.return_value = []\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            self.renewalinfo._update_certificate_table_with_serial_and_aki = MagicMock()\n            self.renewalinfo._get_renewalinfo_data = MagicMock(\n                return_value={\"suggestedWindow\": {\"start\": \"a\", \"end\": \"b\"}}\n            )\n            result = self.renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 200)\n            self.assertIn(\"data\", result)\n            self.renewalinfo._update_certificate_table_with_serial_and_aki.assert_called()\n\n    def test_002_get_returns_404(self):\n        self.mock_repository.get_housekeeping_param.return_value = True\n        self.renewalinfo._get_renewalinfo_data = MagicMock(return_value={})\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = self.renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 404)\n            self.assertEqual(result[\"data\"], \"malf\")\n\n    def test_003_get_returns_400_on_exception(self):\n        self.mock_repository.get_housekeeping_param.return_value = True\n        self.renewalinfo._get_renewalinfo_data = MagicMock(\n            side_effect=Exception(\"fail\")\n        )\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = self.renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 400)\n            self.assertEqual(result[\"data\"], \"malf\")\n\n    def test_004_update_success(self):\n        self.mock_message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"certid\": \"foo\", \"replaced\": True},\n            None,\n        )\n        self.mock_repository.get_certificate_by_certid.return_value = {\n            \"expire_uts\": 100000,\n            \"issue_uts\": 90000,\n        }\n        self.mock_repository.add_certificate.return_value = True\n        with patch(\"acme_srv.renewalinfo.certid_hex_get\", return_value=(None, \"hex\")):\n            result = self.renewalinfo.update(\"content\")\n            self.assertEqual(result[\"code\"], 200)\n\n    def test_005_update_failure(self):\n        self.mock_message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"certid\": \"foo\", \"replaced\": True},\n            None,\n        )\n        self.mock_repository.get_certificate_by_certid.return_value = None\n        with patch(\"acme_srv.renewalinfo.certid_hex_get\", return_value=(None, \"hex\")):\n            result = self.renewalinfo.update(\"content\")\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_006_update_payload_missing(self):\n        self.mock_message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"foo\": \"bar\"},\n            None,\n        )\n        with patch(\"acme_srv.renewalinfo.certid_hex_get\", return_value=(None, \"hex\")):\n            result = self.renewalinfo.update(\"content\")\n            self.assertEqual(result[\"code\"], 400)\n\n    def test_007_lookup_certificate_by_renewalinfo_dot(self):\n        self.renewalinfo._extract_serial_and_aki_from_string = MagicMock(\n            return_value=(\"serial\", \"aki\")\n        )\n        self.renewalinfo._lookup_certificate_by_serial_and_aki = MagicMock(\n            return_value={\"foo\": \"bar\"}\n        )\n        result = self.renewalinfo._lookup_certificate_by_renewalinfo(\"serial.aki\")\n        self.assertEqual(result, {\"foo\": \"bar\"})\n\n    def test_008_lookup_certificate_by_renewalinfo_nodot(self):\n        with patch(\"acme_srv.renewalinfo.certid_hex_get\", return_value=(None, \"hex\")):\n            self.renewalinfo._lookup_certificate_by_certid = MagicMock(\n                return_value={\"foo\": \"bar\"}\n            )\n            result = self.renewalinfo._lookup_certificate_by_renewalinfo(\"foo\")\n            self.assertEqual(result, {\"foo\": \"bar\"})\n\n    def test_009_generate_renewalinfo_window_force(self):\n        cert_dic = {\"expire_uts\": 100000, \"issue_uts\": 90000}\n        self.renewalinfo.config.renewal_force = True\n        with patch(\"acme_srv.renewalinfo.uts_now\", return_value=100000):\n            result = self.renewalinfo._generate_renewalinfo_window(cert_dic)\n            self.assertIn(\"suggestedWindow\", result)\n\n    def test_010_generate_renewalinfo_window_normal(self):\n        cert_dic = {\"expire_uts\": 100000, \"issue_uts\": 90000}\n        self.renewalinfo.config.renewal_force = False\n        result = self.renewalinfo._generate_renewalinfo_window(cert_dic)\n        self.assertIn(\"suggestedWindow\", result)\n\n    def test_011_generate_renewalinfo_window_empty(self):\n        cert_dic = {}\n        result = self.renewalinfo._generate_renewalinfo_window(cert_dic)\n        self.assertEqual(result, {})\n\n    def test_012_generate_renewalinfo_window_no_expire_uts(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        # cert_dic missing 'expire_uts' key\n        result = renewalinfo._generate_renewalinfo_window({\"foo\": \"bar\"})\n        self.assertEqual(result, {})\n        # cert_dic with 'expire_uts' as None\n        result2 = renewalinfo._generate_renewalinfo_window({\"expire_uts\": None})\n        self.assertEqual(result2, {})\n        # cert_dic with 'expire_uts' as 0\n        result3 = renewalinfo._generate_renewalinfo_window({\"expire_uts\": 0})\n        self.assertEqual(result3, {})\n        # cert_dic with 'expire_uts' present, but 'issue_uts' missing: uts_now() should be called\n        with patch(\"acme_srv.renewalinfo.uts_now\", return_value=12345) as mock_uts_now:\n            cert_dic = {\"expire_uts\": 100000}\n            renewalinfo.config.renewal_force = False\n            renewalinfo.config.renewalthreshold_pctg = 85.0\n            renewalinfo._generate_renewalinfo_window(cert_dic)\n            mock_uts_now.assert_called_once()\n\n    def test_013_extract_serial_and_aki_from_string_valid(self):\n        with patch(\"acme_srv.renewalinfo.b64_decode\", return_value=b\"abc\"):\n            with patch(\"acme_srv.renewalinfo.b64_url_recode\", return_value=\"abc\"):\n                result = self.renewalinfo._extract_serial_and_aki_from_string(\"foo.bar\")\n                self.assertEqual(result, (\"616263\", \"616263\"))\n\n    def test_014_extract_serial_and_aki_from_string_invalid(self):\n        result = self.renewalinfo._extract_serial_and_aki_from_string(\"foo\")\n        self.assertEqual(result, (None, None))\n\n    def test_015_load_configuration_all_valid(self):\n        class DummyConfig:\n            def getboolean(self, section, key, fallback=None):\n                return True\n\n            def get(self, section, key, fallback=None):\n                if key == \"renewalthreshold_pctg\":\n                    return \"99.9\"\n                if key == \"retry_after_timeout\":\n                    return \"12345\"\n                return fallback\n\n            def __contains__(self, key):\n                return True\n\n            def __getitem__(self, key):\n                if key == \"CAhandler\":\n                    return {\"handler_file\": \"/dev/null\"}\n                raise KeyError(key)\n\n        with patch(\"acme_srv.renewalinfo.load_config\", return_value=DummyConfig()):\n            self.renewalinfo.logger = MagicMock()\n            self.renewalinfo.config = RenewalinfoConfig()\n            self.renewalinfo._load_configuration()\n            self.assertTrue(self.renewalinfo.config.renewal_force)\n            self.assertEqual(self.renewalinfo.config.renewalthreshold_pctg, 99.9)\n            self.assertEqual(self.renewalinfo.config.retry_after_timeout, 12345)\n\n    def test_016_load_configuration_defaults(self):\n        class DummyConfig:\n            def getboolean(self, section, key, fallback=None):\n                return fallback\n\n            def get(self, section, key, fallback=None):\n                return fallback\n\n            def __contains__(self, key):\n                return True\n\n            def __getitem__(self, key):\n                if key == \"CAhandler\":\n                    return {\"handler_file\": \"/dev/null\"}\n                raise KeyError(key)\n\n        with patch(\"acme_srv.renewalinfo.load_config\", return_value=DummyConfig()):\n            self.renewalinfo.logger = MagicMock()\n            self.renewalinfo.config = RenewalinfoConfig()\n            self.renewalinfo._load_configuration()\n            self.assertFalse(self.renewalinfo.config.renewal_force)\n            self.assertEqual(self.renewalinfo.config.renewalthreshold_pctg, 85.0)\n            self.assertEqual(self.renewalinfo.config.retry_after_timeout, 86400)\n\n    def test_017_load_configuration_renewal_force_error(self):\n        class DummyConfig:\n            def getboolean(self, section, key, fallback=None):\n                raise Exception(\"failbool\")\n\n            def get(self, section, key, fallback=None):\n                return fallback\n\n            def __contains__(self, key):\n                return True\n\n            def __getitem__(self, key):\n                if key == \"CAhandler\":\n                    return {\"handler_file\": \"/dev/null\"}\n                raise KeyError(key)\n\n        with patch(\"acme_srv.renewalinfo.load_config\", return_value=DummyConfig()):\n            self.renewalinfo.logger = MagicMock()\n            self.renewalinfo.config = RenewalinfoConfig()\n            self.renewalinfo._load_configuration()\n            # Should fallback to default False\n            self.assertFalse(self.renewalinfo.config.renewal_force)\n\n    def test_018_load_configuration_renewalthreshold_pctg_error(self):\n        class DummyConfig:\n            def getboolean(self, section, key, fallback=None):\n                return False\n\n            def get(self, section, key, fallback=None):\n                if key == \"renewalthreshold_pctg\":\n                    raise Exception(\"failpctg\")\n                return fallback\n\n            def __contains__(self, key):\n                return True\n\n            def __getitem__(self, key):\n                if key == \"CAhandler\":\n                    return {\"handler_file\": \"/dev/null\"}\n                raise KeyError(key)\n\n        with patch(\"acme_srv.renewalinfo.load_config\", return_value=DummyConfig()):\n            self.renewalinfo.logger = MagicMock()\n            self.renewalinfo.config = RenewalinfoConfig()\n            self.renewalinfo._load_configuration()\n            self.renewalinfo.logger.error.assert_any_call(\n                \"renewalthreshold_pctg parsing error: %s\", unittest.mock.ANY\n            )\n            self.assertEqual(self.renewalinfo.config.renewalthreshold_pctg, 85.0)\n\n    def test_019_load_configuration_retry_after_timeout_error(self):\n        class DummyConfig:\n            def getboolean(self, section, key, fallback=None):\n                return False\n\n            def get(self, section, key, fallback=None):\n                if key == \"renewalthreshold_pctg\":\n                    return \"85.0\"\n                if key == \"retry_after_timeout\":\n                    raise Exception(\"failtimeout\")\n                return fallback\n\n            def __contains__(self, key):\n                return True\n\n            def __getitem__(self, key):\n                if key == \"CAhandler\":\n                    return {\"handler_file\": \"/dev/null\"}\n                raise KeyError(key)\n\n        with patch(\"acme_srv.renewalinfo.load_config\", return_value=DummyConfig()):\n            self.renewalinfo.logger = MagicMock()\n            self.renewalinfo.config = RenewalinfoConfig()\n            self.renewalinfo._load_configuration()\n            self.renewalinfo.logger.error.assert_any_call(\n                \"retry_after_timeout parsing error: %s\", unittest.mock.ANY\n            )\n            self.assertEqual(self.renewalinfo.config.retry_after_timeout, 86400)\n\n    def test_020_exit_does_nothing_and_returns_none(self):\n        renewalinfo = self.renewalinfo\n        # __exit__ should just return None and not raise\n        result = renewalinfo.__exit__(None, None, None)\n        self.assertIsNone(result)\n\n    def test_021_context_manager_usage(self):\n        # Ensure __enter__ and __exit__ work in a with-statement\n        renewalinfo = self.renewalinfo\n        with patch.object(renewalinfo, \"_load_configuration\") as mock_load_config:\n            with renewalinfo as ri:\n                mock_load_config.assert_called_once()\n                self.assertIs(ri, renewalinfo)\n\n    def test_022_update_certificate_table_with_serial_and_aki_success(self):\n        renewalinfo = self.renewalinfo\n        mock_logger = MagicMock()\n        renewalinfo.logger = mock_logger\n        renewalinfo.repository = MagicMock()\n        renewalinfo.dbstore = MagicMock()\n        # Simulate two certs, one valid, one missing cert_raw\n        certs = [\n            {\"cert_raw\": b\"raw1\", \"name\": \"n1\", \"cert\": \"c1\"},\n            {\"name\": \"n2\", \"cert\": \"c2\"},  # missing cert_raw, should be skipped\n        ]\n        renewalinfo.dbstore.certificates_search.return_value = certs\n        with patch(\n            \"acme_srv.renewalinfo.cert_serial_get\", return_value=\"serial1\"\n        ), patch(\"acme_srv.renewalinfo.cert_aki_get\", return_value=\"aki1\"):\n            renewalinfo._update_certificate_table_with_serial_and_aki()\n        # Only one add_certificate should be called\n        renewalinfo.repository.add_certificate.assert_called_once_with(\n            {\n                \"serial\": \"serial1\",\n                \"aki\": \"aki1\",\n                \"name\": \"n1\",\n                \"cert_raw\": b\"raw1\",\n                \"cert\": \"c1\",\n            }\n        )\n        # Should log start and end\n        mock_logger.debug.assert_any_call(\n            \"Renewalinfo._update_certificate_table_with_serial_and_aki()\"\n        )\n        mock_logger.debug.assert_any_call(\n            \"Renewalinfo._update_certificate_table_with_serial_and_aki(%s) - done\", 1\n        )\n\n    def test_023_update_certificate_table_with_serial_and_aki_db_error(self):\n        renewalinfo = self.renewalinfo\n        mock_logger = MagicMock()\n        renewalinfo.logger = mock_logger\n        renewalinfo.repository = MagicMock()\n        renewalinfo.dbstore = MagicMock()\n        renewalinfo.dbstore.certificates_search.side_effect = Exception(\"dbfail\")\n        renewalinfo._update_certificate_table_with_serial_and_aki()\n        # Should log the critical error\n        mock_logger.critical.assert_called_with(\n            \"Database error: failed to retrieve certificate list for renewal info update: %s\",\n            unittest.mock.ANY,\n        )\n        # Should log end with 0\n        mock_logger.debug.assert_any_call(\n            \"Renewalinfo._update_certificate_table_with_serial_and_aki(%s) - done\", 0\n        )\n        # No add_certificate calls\n        renewalinfo.repository.add_certificate.assert_not_called()\n\n    def test_024_get_compat_success(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.config.retry_after_timeout = 123\n        renewalinfo.repository.get_housekeeping_param.return_value = True\n        renewalinfo._get_renewalinfo_data = MagicMock(return_value={\"foo\": \"bar\"})\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 200)\n            self.assertIn(\"data\", result)\n            self.assertIn(\"header\", result)\n\n    def test_025_get_compat_404(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.repository.get_housekeeping_param.return_value = True\n        renewalinfo._get_renewalinfo_data = MagicMock(return_value={})\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 404)\n            self.assertEqual(result[\"data\"], \"malf\")\n\n    def test_026_get_compat_400(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.repository.get_housekeeping_param.return_value = True\n        renewalinfo._get_renewalinfo_data = MagicMock(side_effect=Exception(\"fail\"))\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 400)\n            self.assertEqual(result[\"data\"], \"malf\")\n\n    def test_027_update_compat_success(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.message = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"certid\": \"foo\", \"replaced\": True},\n            None,\n        )\n        renewalinfo._lookup_certificate_by_renewalinfo = MagicMock(\n            return_value={\"foo\": \"bar\"}\n        )\n        renewalinfo.repository.add_certificate.return_value = True\n        result = renewalinfo.update(\"content\")\n        self.assertEqual(result[\"code\"], 200)\n\n    def test_028_update_compat_failure(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.message = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"certid\": \"foo\", \"replaced\": True},\n            None,\n        )\n        renewalinfo._lookup_certificate_by_renewalinfo = MagicMock(return_value=None)\n        result = renewalinfo.update(\"content\")\n        self.assertEqual(result[\"code\"], 400)\n\n    def test_029_update_compat_payload_missing(self):\n        renewalinfo = self.renewalinfo\n        renewalinfo.logger = MagicMock()\n        renewalinfo.message = MagicMock()\n        renewalinfo.repository = MagicMock()\n        renewalinfo.err_msg_dic = {\"malformed\": \"malf\"}\n        renewalinfo.message.check.return_value = (\n            200,\n            None,\n            None,\n            None,\n            {\"foo\": \"bar\"},\n            None,\n        )\n        result = renewalinfo.update(\"content\")\n        self.assertEqual(result[\"code\"], 400)\n\n    def test_030_lookup_certificate_by_serial_and_aki_found(self):\n        # Setup: cert_list contains a cert with matching aki\n        cert = {\"aki\": \"aki123\", \"foo\": \"bar\"}\n        self.renewalinfo.repository.get_certificates_by_serial.return_value = [cert]\n        result = self.renewalinfo._lookup_certificate_by_serial_and_aki(\n            \"serial123\", \"aki123\"\n        )\n        self.assertEqual(result, cert)\n        self.renewalinfo.repository.get_certificates_by_serial.assert_called_once_with(\n            \"serial123\"\n        )\n\n    def test_031_lookup_certificate_by_serial_and_aki_leading_zero(self):\n        # Setup: first call returns empty, second returns a cert with matching aki\n        cert = {\"aki\": \"aki456\", \"foo\": \"baz\"}\n        self.renewalinfo.repository.get_certificates_by_serial.side_effect = [\n            [],\n            [cert],\n        ]\n        result = self.renewalinfo._lookup_certificate_by_serial_and_aki(\n            \"0123\", \"aki456\"\n        )\n        self.assertEqual(result, cert)\n        self.assertEqual(\n            self.renewalinfo.repository.get_certificates_by_serial.call_count, 2\n        )\n        self.renewalinfo.repository.get_certificates_by_serial.assert_any_call(\"0123\")\n        self.renewalinfo.repository.get_certificates_by_serial.assert_any_call(\"123\")\n\n    def test_032_lookup_certificate_by_serial_and_aki_not_found(self):\n        # Setup: cert_list does not contain a cert with matching aki\n        self.renewalinfo.repository.get_certificates_by_serial.return_value = [\n            {\"aki\": \"other\"}\n        ]\n        result = self.renewalinfo._lookup_certificate_by_serial_and_aki(\n            \"serial\", \"aki999\"\n        )\n        self.assertEqual(result, {})\n\n    def test_033_lookup_certificate_by_serial_and_aki_empty_list(self):\n        # Setup: cert_list is empty\n        self.renewalinfo.repository.get_certificates_by_serial.return_value = []\n        result = self.renewalinfo._lookup_certificate_by_serial_and_aki(\"serial\", \"aki\")\n        self.assertEqual(result, {})\n\n    def test_034_get_renewalinfo_data(self):\n        # Setup: _lookup_certificate_by_renewalinfo and _generate_renewalinfo_window are called\n        cert_dic = {\"expire_uts\": 100000, \"issue_uts\": 90000}\n        renewalinfo_dic = {\n            \"suggestedWindow\": {\"start\": \"2025-01-01\", \"end\": \"2026-01-01\"}\n        }\n        self.renewalinfo._lookup_certificate_by_renewalinfo = MagicMock(\n            return_value=cert_dic\n        )\n        self.renewalinfo._generate_renewalinfo_window = MagicMock(\n            return_value=renewalinfo_dic\n        )\n        result = self.renewalinfo._get_renewalinfo_data(\"foo.bar\")\n        self.renewalinfo._lookup_certificate_by_renewalinfo.assert_called_once_with(\n            \"foo.bar\"\n        )\n        self.renewalinfo._generate_renewalinfo_window.assert_called_once_with(cert_dic)\n        self.assertEqual(result, renewalinfo_dic)\n\n    def test_009__load_ca_handler_success(self):\n        # Patch ca_handler_load to return a mock module with CAhandler attribute\n        mock_cahandler_class = MagicMock()\n        mock_module = MagicMock()\n        mock_module.CAhandler = mock_cahandler_class\n        with patch(\"acme_srv.renewalinfo.ca_handler_load\", return_value=mock_module):\n            self.renewalinfo.cahandler = None\n            self.renewalinfo._load_ca_handler(\n                {\"CAhandler\": {\"handler_file\": \"/dev/null\"}}\n            )\n            self.assertIs(self.renewalinfo.cahandler, mock_cahandler_class)\n\n    def test_010__load_ca_handler_failure(self):\n        # Patch ca_handler_load to return None\n        with patch(\"acme_srv.renewalinfo.ca_handler_load\", return_value=None):\n            self.renewalinfo.cahandler = None\n            self.renewalinfo._load_ca_handler(\n                {\"CAhandler\": {\"handler_file\": \"/dev/null\"}}\n            )\n            self.assertIsNone(self.renewalinfo.cahandler)\n            self.mock_logger.critical.assert_called_with(\"No ca_handler loaded\")\n\n    def test_011_get_with_cahandler_lookup(self):\n        # Simulate config.renewalinfo_lookup True and cahandler with lookup_renewalinfo\n        self.renewalinfo.config.renewalinfo_lookup = True\n        self.renewalinfo.config.acme_url = \"https://acme.example.com\"\n        # Create a mock instance for the context manager\n        mock_cahandler_instance = MagicMock()\n        mock_cahandler_instance.lookup_renewalinfo.return_value = (201, {\"foo\": \"bar\"})\n        # Create a mock class that returns the instance as context manager\n        mock_cahandler_class = MagicMock()\n        mock_cahandler_class.return_value.__enter__.return_value = (\n            mock_cahandler_instance\n        )\n        mock_cahandler_class.return_value.__exit__.return_value = None\n        self.renewalinfo.cahandler = mock_cahandler_class\n        with patch(\"acme_srv.renewalinfo.string_sanitize\", return_value=\"foo\"):\n            result = self.renewalinfo.get(\"/acme/renewal-info/foo\")\n            self.assertEqual(result[\"code\"], 201)\n            self.assertIn(\"data\", result)\n            self.assertEqual(result[\"data\"], {\"foo\": \"bar\"})\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_signature.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport configparser\nfrom unittest.mock import patch, MagicMock\n\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.signature import Signature\n\n        self.signature = Signature(False, \"http://tester.local\", self.logger)\n\n    def test_001_signature__jwk_load(self):\n        \"\"\"test jwk load\"\"\"\n        # Mock the dbstore instance on the existing signature object\n        self.signature.dbstore = MagicMock()\n        self.signature.dbstore.jwk_load.return_value = \"foo\"\n\n        self.assertEqual(\"foo\", self.signature._jwk_loader(1))\n\n    def test_002_signature_check(self):\n        \"\"\"test Signature.check() without having content\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\", None),\n            self.signature.check(\"foo\", None),\n        )\n\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_003_signature_check(self, mock_jwk):\n        \"\"\"test Signature.check() while pubkey lookup failed\"\"\"\n        mock_jwk.return_value = {}\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(\"foo\", 1),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_004_signature_check(self, mock_jwk, mock_sig):\n        \"\"\"test successful Signature.check()\"\"\"\n        mock_jwk.return_value = {\"foo\": \"bar\"}\n        mock_sig.return_value = (True, None)\n        self.assertEqual((True, None, None), self.signature.check(\"foo\", 1))\n\n    def test_005_signature_check(self):\n        \"\"\"test successful Signature.check() without account_name and use_emb_key False\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(None, 1, False),\n        )\n\n    def test_006_signature_check(self):\n        \"\"\"test successful Signature.check() without account_name and use_emb_key True but having a corrupted protected header\"\"\"\n        protected = {\"foo\": \"foo\"}\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(None, 1, True, protected),\n        )\n\n    @patch(\"acme_srv.signature.DBstore\")\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_007_signature_check(self, mock_sig, mock_dbstore_class):\n        \"\"\"test successful Signature.check() with account_name and use_emb_key True, sigcheck returns something\"\"\"\n        # Setup dbstore mock to return a key\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.jwk_load.return_value = {\"key\": \"value\"}\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Setup signature_check mock\n        mock_sig.return_value = (\"result\", \"error\")\n\n        # Create a new signature instance with the mocked dbstore\n        from acme_srv.signature import Signature\n\n        signature = Signature(False, \"http://tester.local\", self.logger)\n\n        self.assertEqual((\"result\", \"error\", None), signature.check(\"foo\", 1, True))\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_008_signature_check(self, mock_sig):\n        \"\"\"test successful Signature.check() without account_name and use_emb_key True, sigcheck returns something\"\"\"\n        mock_sig.return_value = (\"result\", \"error\")\n        protected = {\"url\": \"url\", \"jwk\": \"jwk\"}\n        self.assertEqual(\n            (\"result\", \"error\", None), self.signature.check(None, 1, True, protected)\n        )\n\n    @patch(\"acme_srv.signature.DBstore\")\n    def test_009_signature__jwk_load(self, mock_dbstore_class):\n        \"\"\"test jwk load  - dbstore.jwk_load() raises an exception\"\"\"\n        # Setup mock to raise exception\n        mock_dbstore_instance = MagicMock()\n        mock_dbstore_instance.jwk_load.side_effect = Exception(\"exc_sig_jw_load\")\n        mock_dbstore_class.return_value = mock_dbstore_instance\n\n        # Create a new signature instance with the mocked dbstore\n        from acme_srv.signature import Signature\n\n        signature = Signature(False, \"http://tester.local\", self.logger)\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            signature._jwk_loader(1)\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to load JWK for account id 1: exc_sig_jw_load\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_010_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  result and error\"\"\"\n        content = \"content\"\n        mac_key = \"mac_key\"\n        mock_sigchk.return_value = (\"result\", \"error\")\n        self.assertEqual(\n            (\"result\", \"error\"), self.signature.eab_check(content, mac_key)\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_011_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  result no error\"\"\"\n        content = \"content\"\n        mac_key = \"mac_key\"\n        mock_sigchk.return_value = (\"result\", None)\n        self.assertEqual((\"result\", None), self.signature.eab_check(content, mac_key))\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_012_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  result false and  error\"\"\"\n        content = \"content\"\n        mac_key = \"mac_key\"\n        mock_sigchk.return_value = (False, \"error\")\n        self.assertEqual((False, \"error\"), self.signature.eab_check(content, mac_key))\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_013_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  content None\"\"\"\n        content = None\n        mac_key = \"mac_key\"\n        mock_sigchk.return_value = (False, \"error\")\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\"),\n            self.signature.eab_check(content, mac_key),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_014_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  mac_key None\"\"\"\n        content = \"content\"\n        mac_key = None\n        mock_sigchk.return_value = (False, \"error\")\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\"),\n            self.signature.eab_check(content, mac_key),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_015_signature_eab_check(self, mock_sigchk):\n        \"\"\"test eab_check  -  mac_key and content None\"\"\"\n        content = None\n        mac_key = None\n        mock_sigchk.return_value = (False, \"error\")\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\"),\n            self.signature.eab_check(content, mac_key),\n        )\n\n    @patch(\"acme_srv.signature.load_config\")\n    def test_016__init(self, mock_load_cfg):\n        \"\"\"test _config_load account with url prefix without tailing slash configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Directory\"] = {\"foo\": \"bar\", \"url_prefix\": \"url_prefix\"}\n        mock_load_cfg.return_value = parser\n        self.signature.__init__(False, \"http://tester.local\", self.logger)\n        self.assertEqual(\"url_prefix/acme/revokecert\", self.signature.revocation_path)\n\n    @patch(\"acme_srv.signature.load_config\")\n    def test_017__init(self, mock_load_cfg):\n        \"\"\"test _config_load account with url prefix without tailing slash configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Directory\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.signature.__init__(False, \"http://tester.local\", self.logger)\n        self.assertEqual(\"/acme/revokecert\", self.signature.revocation_path)\n\n    @patch(\"acme_srv.signature.load_config\")\n    def test_016__init_empty(self, mock_load_cfg):\n        \"\"\"test _config_load account with url prefix without tailing slash configured\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAHandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.signature.__init__(False, \"http://tester.local\", self.logger)\n        self.assertEqual(\"/acme/revokecert\", self.signature.revocation_path)\n\n    def test_018_signature_check(self):\n        \"\"\"test Signature.cli_check() without having aname\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.cli_check(None, \"content\"),\n        )\n\n    def test_019_signature_check(self):\n        \"\"\"test Signature.check() without having content\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\", None),\n            self.signature.cli_check(\"foo\", None),\n        )\n\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_020_signature_check(self, mock_jwk):\n        \"\"\"test Signature.check() while pubkey lookup failed\"\"\"\n        mock_jwk.return_value = {}\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.cli_check(\"foo\", 1),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_021_signature_check(self, mock_jwk, mock_sig):\n        \"\"\"test successful Signature.check()\"\"\"\n        mock_jwk.return_value = {\"foo\": \"bar\"}\n        mock_sig.return_value = (True, None)\n        self.assertEqual((True, None, None), self.signature.cli_check(\"foo\", 1))\n\n    @patch(\"acme_srv.signature.signature_check\")\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_022_signature_check(self, mock_jwk, mock_sig):\n        \"\"\"test successful Signature.check() without account_name  sigcheck returns something\"\"\"\n        mock_jwk.return_value = {\"foo\": \"bar\"}\n        mock_sig.return_value = (\"result\", \"error\")\n        self.assertEqual((\"result\", \"error\", None), self.signature.cli_check(\"foo\", 1))\n\n    def test_023_cli_check_no_content(self):\n        \"\"\"Signature.cli_check() returns malformed error if content is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\", None),\n            self.signature.cli_check(\"foo\", None),\n        )\n\n    def test_024_cli_check_no_aname(self):\n        \"\"\"Signature.cli_check() returns accountDoesNotExist error if aname is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.cli_check(None, \"content\"),\n        )\n\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_025_cli_check_pubkey_none(self, mock_jwk):\n        \"\"\"Signature.cli_check() returns accountDoesNotExist error if pubkey is None\"\"\"\n        mock_jwk.return_value = None\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.cli_check(\"foo\", \"content\"),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_026_cli_check_success(self, mock_jwk, mock_sig):\n        \"\"\"Signature.cli_check() returns result from signature_check\"\"\"\n        mock_jwk.return_value = {\"foo\": \"bar\"}\n        mock_sig.return_value = (True, None)\n        self.assertEqual((True, None, None), self.signature.cli_check(\"foo\", \"content\"))\n\n    def test_027_check_no_content(self):\n        \"\"\"Signature.check() returns malformed error if content is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\", None),\n            self.signature.check(\"foo\", None),\n        )\n\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_028_check_pubkey_none(self, mock_jwk):\n        \"\"\"Signature.check() returns accountDoesNotExist error if pubkey is None\"\"\"\n        mock_jwk.return_value = None\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(\"foo\", \"content\"),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    @patch(\"acme_srv.signature.Signature._jwk_loader\")\n    def test_029_check_success(self, mock_jwk, mock_sig):\n        \"\"\"Signature.check() returns result from signature_check\"\"\"\n        mock_jwk.return_value = {\"foo\": \"bar\"}\n        mock_sig.return_value = (True, None)\n        self.assertEqual((True, None, None), self.signature.check(\"foo\", \"content\"))\n\n    def test_030_check_emb_key_no_protected(self):\n        \"\"\"Signature.check() returns accountDoesNotExist error if use_emb_key True but protected is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(None, \"content\", True, None),\n        )\n\n    def test_031_check_emb_key_no_jwk(self):\n        \"\"\"Signature.check() returns accountDoesNotExist error if use_emb_key True but protected lacks jwk\"\"\"\n        protected = {\"foo\": \"bar\"}\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(None, \"content\", True, protected),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_032_check_emb_key_success(self, mock_sig):\n        \"\"\"Signature.check() returns result from signature_check with embedded jwk\"\"\"\n        protected = {\"jwk\": {\"foo\": \"bar\"}}\n        mock_sig.return_value = (True, None)\n        self.assertEqual(\n            (True, None, None), self.signature.check(None, \"content\", True, protected)\n        )\n\n    def test_033_check_no_aname_no_emb_key(self):\n        \"\"\"Signature.check() returns accountDoesNotExist error if no aname and use_emb_key False\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:accountDoesNotExist\", None),\n            self.signature.check(None, \"content\", False),\n        )\n\n    def test_034_eab_check_no_content(self):\n        \"\"\"Signature.eab_check() returns malformed error if content is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\"),\n            self.signature.eab_check(None, \"mac_key\"),\n        )\n\n    def test_035_eab_check_no_mac_key(self):\n        \"\"\"Signature.eab_check() returns malformed error if mac_key is None\"\"\"\n        self.assertEqual(\n            (False, \"urn:ietf:params:acme:error:malformed\"),\n            self.signature.eab_check(\"content\", None),\n        )\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_036_eab_check_success(self, mock_sig):\n        \"\"\"Signature.eab_check() returns result from signature_check\"\"\"\n        mock_sig.return_value = (True, None)\n        self.assertEqual((True, None), self.signature.eab_check(\"content\", \"mac_key\"))\n\n    @patch(\"acme_srv.signature.signature_check\")\n    def test_037_eab_check_error(self, mock_sig):\n        \"\"\"Signature.eab_check() returns error from signature_check\"\"\"\n        mock_sig.return_value = (False, \"error\")\n        self.assertEqual(\n            (False, \"error\"), self.signature.eab_check(\"content\", \"mac_key\")\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_trigger.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for account.py\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212\nimport unittest\nimport sys\nimport importlib\nimport configparser\nfrom unittest.mock import patch, MagicMock, Mock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for ACMEHandler\"\"\"\n\n    acme = None\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        models_mock = MagicMock()\n        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore\n        modules = {\"acme_srv.db_handler\": models_mock}\n        patch.dict(\"sys.modules\", modules).start()\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        from acme_srv.trigger import Trigger\n        from acme_srv.order import Order\n\n        self.order = Order(False, \"http://tester.local\", self.logger)\n        self.trigger = Trigger(False, \"http://tester.local\", self.logger)\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.certificate.Certificate.certlist_search\")\n    @patch(\"acme_srv.trigger.cert_pubkey_get\")\n    def test_001_trigger__certname_lookup(\n        self, mock_cert_pub, mock_search_list, mock_import\n    ):\n        \"\"\"trigger._certname_lookup() failed bcs. of empty certificate list\"\"\"\n        mock_cert_pub.return_value = \"foo\"\n        mock_search_list.return_value = []\n        mock_import.return_value = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.assertEqual([], self.trigger._certname_lookup(\"cert_pem\"))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.certificate.Certificate.certlist_search\")\n    @patch(\"acme_srv.trigger.cert_pubkey_get\")\n    def test_002_trigger__certname_lookup(\n        self, mock_cert_pub, mock_search_list, mock_import\n    ):\n        \"\"\"trigger._certname_lookup() failed bcs. of wrong certificate list\"\"\"\n        mock_cert_pub.return_value = \"foo\"\n        mock_search_list.return_value = [{\"foo\": \"bar\"}]\n        mock_import.return_value = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.assertEqual([], self.trigger._certname_lookup(\"cert_pem\"))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.certificate.Certificate.certlist_search\")\n    @patch(\"acme_srv.trigger.cert_pubkey_get\")\n    def test_003_trigger__certname_lookup(\n        self, mock_cert_pub, mock_search_list, mock_import\n    ):\n        \"\"\"trigger._certname_lookup() failed bcs. of emty csr field\"\"\"\n        mock_cert_pub.return_value = \"foo\"\n        mock_search_list.return_value = [{\"csr\": None}]\n        mock_import.return_value = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.assertEqual([], self.trigger._certname_lookup(\"cert_pem\"))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.trigger.csr_pubkey_get\")\n    @patch(\"acme_srv.certificate.Certificate.certlist_search\")\n    @patch(\"acme_srv.trigger.cert_pubkey_get\")\n    def test_004_trigger__certname_lookup(\n        self, mock_cert_pub, mock_search_list, mock_csr_pub, mock_import\n    ):\n        \"\"\"trigger._certname_lookup() failed bcs. of emty csr field\"\"\"\n        mock_cert_pub.return_value = \"foo\"\n        mock_csr_pub.return_value = \"foo1\"\n        mock_search_list.return_value = [{\"csr\": None}]\n        mock_import.return_value = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.assertEqual([], self.trigger._certname_lookup(\"cert_pem\"))\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.trigger.csr_pubkey_get\")\n    @patch(\"acme_srv.certificate.Certificate.certlist_search\")\n    @patch(\"acme_srv.trigger.cert_pubkey_get\")\n    def test_005_trigger__certname_lookup(\n        self, mock_cert_pub, mock_search_list, mock_csr_pub, mock_import\n    ):\n        \"\"\"trigger._certname_lookup() failed bcs. of emty csr field\"\"\"\n        mock_cert_pub.return_value = \"foo\"\n        mock_csr_pub.return_value = \"foo\"\n        mock_search_list.return_value = [\n            {\"csr\": \"csr\", \"name\": \"cert_name\", \"order__name\": \"order_name\"}\n        ]\n        mock_import.return_value = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.assertEqual(\n            [{\"cert_name\": \"cert_name\", \"order_name\": \"order_name\"}],\n            self.trigger._certname_lookup(\"cert_pem\"),\n        )\n\n    def test_006_trigger_parse(self):\n        \"\"\"Trigger.parse() with empty payload\"\"\"\n        payload = \"\"\n        result = {\n            \"header\": {},\n            \"code\": 400,\n            \"data\": {\"detail\": \"payload missing\", \"type\": \"malformed\", \"status\": 400},\n        }\n        self.assertEqual(result, self.trigger.parse(payload))\n\n    def test_007_trigger_parse(self):\n        \"\"\"Trigger.parse() with wrong payload\"\"\"\n        payload = '{\"foo\": \"bar\"}'\n        result = {\n            \"header\": {},\n            \"code\": 400,\n            \"data\": {\"detail\": \"payload missing\", \"type\": \"malformed\", \"status\": 400},\n        }\n        self.assertEqual(result, self.trigger.parse(payload))\n\n    def test_008_trigger_parse(self):\n        \"\"\"Trigger.parse() with empty payload key\"\"\"\n        payload = '{\"payload\": \"\"}'\n        result = {\n            \"header\": {},\n            \"code\": 400,\n            \"data\": {\"detail\": \"payload empty\", \"type\": \"malformed\", \"status\": 400},\n        }\n        self.assertEqual(result, self.trigger.parse(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._payload_process\")\n    def test_009_trigger_parse(self, mock_process):\n        \"\"\"Trigger.parse() with payload mock result 400\"\"\"\n        payload = '{\"payload\": \"foo\"}'\n        mock_process.return_value = (400, \"message\", \"detail\")\n        result = {\n            \"header\": {},\n            \"code\": 400,\n            \"data\": {\"detail\": \"detail\", \"type\": \"message\", \"status\": 400},\n        }\n        self.assertEqual(result, self.trigger.parse(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._payload_process\")\n    def test_010_trigger_parse(self, mock_process):\n        \"\"\"Trigger.parse() with payload mock result 200\"\"\"\n        payload = '{\"payload\": \"foo\"}'\n        mock_process.return_value = (200, \"message\", \"detail\")\n        result = {\n            \"header\": {},\n            \"code\": 200,\n            \"data\": {\"detail\": \"detail\", \"type\": \"message\", \"status\": 200},\n        }\n        self.assertEqual(result, self.trigger.parse(payload))\n\n    def test_011_trigger__payload_process(self):\n        \"\"\"Trigger._payload_process() without payload\"\"\"\n        payload = {}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", None, None))\n        self.assertEqual(\n            (400, \"payload malformed\", None), self.trigger._payload_process(payload)\n        )\n\n    def test_012_trigger__payload_process(self):\n        \"\"\"Trigger._payload_process() without certbunde and cert_raw\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", None, None))\n        self.assertEqual((400, \"error\", None), self.trigger._payload_process(payload))\n\n    def test_013_trigger__payload_process(self):\n        \"\"\"Trigger._payload_process() with bundle and without cart_raw\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", None))\n        self.assertEqual((400, \"error\", None), self.trigger._payload_process(payload))\n\n    def test_014_trigger__payload_process(self):\n        \"\"\"Trigger._payload_process() with bundle and without cart_raw\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", None, \"raw\"))\n        self.assertEqual((400, \"error\", None), self.trigger._payload_process(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_015_trigger__payload_process(\n        self, mock_cobystr, mock_der2pem, mock_b64dec, mock_lookup\n    ):\n        \"\"\"Trigger._payload_process() with certificae_name\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", \"raw\"))\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [\n            {\"cert_name\": \"certificate_name\", \"order_name\": None}\n        ]\n        self.assertEqual((200, \"OK\", None), self.trigger._payload_process(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_016_trigger__payload_process(\n        self, mock_cobystr, mock_der2pem, mock_b64dec, mock_lookup\n    ):\n        \"\"\"Trigger._payload_process() without certificate_name\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", \"raw\"))\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [{\"cert_name\": None, \"order_name\": \"order_name\"}]\n        self.assertEqual((200, \"OK\", None), self.trigger._payload_process(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_017_trigger__payload_process(\n        self, mock_cobystr, mock_der2pem, mock_b64dec, mock_lookup\n    ):\n        \"\"\"Trigger._payload_process() _certname.lookup() returned empty list\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", \"raw\"))\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = []\n        self.assertEqual(\n            (400, \"certificate_name lookup failed\", None),\n            self.trigger._payload_process(payload),\n        )\n\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_018_trigger__payload_process(\n        self, mock_cobystr, mock_der2pem, mock_b64dec, mock_lookup\n    ):\n        \"\"\"Trigger._payload_process() without certificate_name\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", \"raw\"))\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [\n            {\"cert_name\": \"certificate_name\", \"order_name\": \"order_name\"}\n        ]\n        self.order.dbstore.order_update.return_value = None\n        self.assertEqual((200, \"OK\", None), self.trigger._payload_process(payload))\n\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_019_trigger__payload_process(\n        self, mock_cobystr, mock_der2pem, mock_b64dec, mock_lookup\n    ):\n        \"\"\"Trigger._payload_process() without certificate_name\"\"\"\n        payload = {\"payload\": \"foo\"}\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(return_value=(\"error\", \"bundle\", \"raw\"))\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [\n            {\"cert_name\": \"certificate_name1\", \"order_name\": \"order_name1\"},\n            {\"cert_name\": \"certificate_name2\", \"order_name\": \"order_name2\"},\n        ]\n        self.assertEqual((200, \"OK\", None), self.trigger._payload_process(payload))\n\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_020_trigger__payload_process(\n        self, mock_cobystr, mock_lookup, mock_der2pem, mock_b64dec\n    ):\n        \"\"\"test Trigger._payload_process - dbstore.order_update() raises an exception\"\"\"\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(\n            return_value=(None, \"certificate\", \"certificate_raw\")\n        )\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [\n            {\"cert_name\": \"certificate_name1\", \"order_name\": \"order_name1\"},\n            {\"cert_name\": \"certificate_name2\", \"order_name\": \"order_name2\"},\n        ]\n        self.trigger.dbstore.certificate_add.return_value = True\n        self.trigger.dbstore.order_update.side_effect = Exception(\n            \"exc_trigger_order_upd\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._payload_process(\"payload\")\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to update order status during trigger processing: exc_trigger_order_upd\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.trigger.b64_decode\")\n    @patch(\"acme_srv.trigger.cert_der2pem\")\n    @patch(\"acme_srv.trigger.Trigger._certname_lookup\")\n    @patch(\"acme_srv.trigger.convert_byte_to_string\")\n    def test_021_trigger__payload_process(\n        self, mock_cobystr, mock_lookup, mock_der2pem, mock_b64dec\n    ):\n        \"\"\"test Trigger._payload_process - dbstore.order_update() raises an exception\"\"\"\n        ca_handler_module = importlib.import_module(\n            \"examples.ca_handler.skeleton_ca_handler\"\n        )\n        self.trigger.cahandler = ca_handler_module.CAhandler\n        self.trigger.cahandler.trigger = Mock(\n            return_value=(None, \"certificate\", \"certificate_raw\")\n        )\n        mock_der2pem.return_value = \"der2pem\"\n        mock_cobystr.return_value = \"cert_pem\"\n        mock_b64dec.return_value = \"b64dec\"\n        mock_lookup.return_value = [\n            {\"cert_name\": \"certificate_name1\", \"order_name\": \"order_name1\"},\n            {\"cert_name\": \"certificate_name2\", \"order_name\": \"order_name2\"},\n        ]\n        self.trigger.dbstore.certificate_add.side_effect = Exception(\n            \"exc_trigger_order_add\"\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._payload_process(\"payload\")\n        self.assertIn(\n            \"CRITICAL:test_a2c:Database error: failed to update order status during trigger processing: exc_trigger_order_upd\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_022_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load missing ca_handler\"\"\"\n        mock_load_cfg.return_value = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler configuration missing in config file\", lcm.output\n        )\n\n    @patch(\"acme_srv.trigger.Trigger._config_load\")\n    def test_023__enter__(self, mock_cfg):\n        \"\"\"test enter\"\"\"\n        mock_cfg.return_value = True\n        self.trigger.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_024_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        # parser['Account'] = {'foo': 'bar'}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertFalse(self.trigger.tnauthlist_support)\n        self.assertIn(\n            \"ERROR:test_a2c:CAhandler configuration missing in config file\", lcm.output\n        )\n\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_025_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load bogus ca_handler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"handler_file\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Loading CAhandler configured in cfg failed with err: 'NoneType' object has no attribute 'loader'\",\n            lcm.output,\n        )\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_026_config_load(self, mock_load_cfg, mock_imp):\n        \"\"\"test _config_load missing ca_handler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"handler_file\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        mock_imp.return_value = Mock()\n        self.trigger._config_load()\n        self.assertTrue(self.trigger.cahandler)\n\n    @patch(\"importlib.import_module\")\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_027_config_load(self, mock_load_cfg, mock_imp):\n        \"\"\"test _config_load missing ca_handler\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        mock_imp.return_value = Mock()\n        self.trigger._config_load()\n        self.assertTrue(self.trigger.cahandler)\n\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_028_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Order\"] = {\"tnauthlist_support\": False}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertFalse(self.trigger.tnauthlist_support)\n\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_029_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load empty config\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Order\"] = {\"tnauthlist_support\": True}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertTrue(self.trigger.tnauthlist_support)\n\n    @patch(\"acme_srv.trigger.ca_handler_load\")\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_030_config_load(self, mock_load_cfg, mock_cahandler_load):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Foo\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        mock_cahandler_load.return_value = \"foo\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.trigger._config_load()\n        self.assertIn(\n            \"CRITICAL:test_a2c:Failed to load CA handler module: 'str' object has no attribute 'CAhandler'\",\n            lcm.output,\n        )\n\n    @patch(\"acme_srv.trigger.ca_handler_load\")\n    @patch(\"acme_srv.trigger.load_config\")\n    def test_031_config_load(self, mock_load_cfg, mock_cahandler_load):\n        \"\"\"test _config_load()\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"Foo\"] = {\"foo\": \"bar\"}\n        mock_load_cfg.return_value = parser\n        self.trigger._config_load()\n        self.assertTrue(self.trigger.cahandler)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_vault_handler.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"unittests for vault_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, W0212\nimport sys\nimport os\nimport unittest\nimport requests\nfrom unittest.mock import Mock, MagicMock, patch\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass TestCAhandler(unittest.TestCase):\n    def setUp(self):\n        import logging\n        from examples.ca_handler.vault_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n\n    def test_001_trigger_not_implemented(self):\n        error, cert_bundle, cert_raw = self.cahandler.trigger(\"payload\")\n        self.assertEqual(error, \"Method not implemented.\")\n        self.assertIsNone(cert_bundle)\n        self.assertIsNone(cert_raw)\n\n    def test_002_poll_not_implemented(self):\n        error, cert_bundle, cert_raw, poll_identifier, rejected = self.cahandler.poll(\n            \"cert_name\", \"poll_identifier\", \"csr\"\n        )\n        self.assertEqual(error, \"Method not implemented.\")\n        self.assertIsNone(cert_bundle)\n        self.assertIsNone(cert_raw)\n\n    def test_003_config_check_missing_params(self):\n\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"vault_url parameter is missing in config file\",\n                self.cahandler._config_check(),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Configuration check ended with error: vault_url parameter is missing in config file\",\n            lcm.output,\n        )\n\n    def test_004_config_check_all_params_present(self):\n        self.cahandler.vault_url = \"url\"\n        self.cahandler.vault_path = \"path\"\n        self.cahandler.vault_role = \"role\"\n        self.cahandler.vault_token = \"token\"\n        error = self.cahandler._config_check()\n        self.assertIsNone(error)\n\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._config_load\")\n    def test_005__enter__(self, mock_cfg):\n        \"\"\"test enter  called\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._config_load\")\n    def test_006__enter__(self, mock_cfg):\n        \"\"\"test enter api hosts defined\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.vault_url = \"vault_url\"\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    @patch.object(requests, \"post\")\n    def test_007__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_post(\"url\", \"data\")\n        )\n\n    @patch(\"requests.post\")\n    def test_008__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_post(\"url\", \"data\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.post\")\n    def test_009__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.text = None\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_post(\"url\", \"data\"))\n\n    @patch(\"requests.post\")\n    def test_010__api_post(self, mock_req):\n        \"\"\"test _api_post()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_post\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_post\"), self.cahandler._api_post(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_post\", lcm.output\n        )\n\n    @patch.object(requests, \"get\")\n    def test_011__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_get(\"url\")\n        )\n\n    @patch(\"requests.get\")\n    def test_012__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_get(\"url\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.get\")\n    def test_013__api_get(self, mock_req):\n        \"\"\"test _api_get()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_get\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((500, \"exc_api_get\"), self.cahandler._api_get(\"url\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_get\", lcm.output\n        )\n\n    @patch.object(requests, \"put\")\n    def test_014__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = lambda: {\"foo\": \"bar\"}\n        mock_req.return_value = mockresponse\n        self.assertEqual(\n            (\"status_code\", {\"foo\": \"bar\"}), self.cahandler._api_put(\"url\", \"data\")\n        )\n\n    @patch(\"requests.put\")\n    def test_015__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.json = \"aaaa\"\n        mock_req.return_value = mockresponse\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (\"status_code\", \"'str' object is not callable\"),\n                self.cahandler._api_put(\"url\", \"data\"),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error during json parsing: 'str' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"requests.put\")\n    def test_016__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        mockresponse = Mock()\n        mockresponse.status_code = \"status_code\"\n        mockresponse.text = None\n        mock_req.return_value = mockresponse\n        self.assertEqual((\"status_code\", None), self.cahandler._api_put(\"url\", \"data\"))\n\n    @patch(\"requests.put\")\n    def test_017__api_put(self, mock_req):\n        \"\"\"test _api_put()\"\"\"\n        self.cahandler.api_host = \"api_host\"\n        self.cahandler.auth = \"auth\"\n        mock_req.side_effect = Exception(\"exc_api_put\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (500, \"exc_api_put\"), self.cahandler._api_put(\"url\", \"data\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Request_operation returned error: exc_api_put\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.vault_ca_handler.load_config\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_eab_profile_load\",\n        return_value=(True, \"handler\"),\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_proxy_load\",\n        return_value={\"http\": \"proxy\"},\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_profile_load\",\n        return_value={\"profile\": \"data\"},\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_headerinfo_load\", return_value=True\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_enroll_config_log_load\",\n        return_value=(True, [\"skip1\", \"skip2\"]),\n    )\n    def test_018_config_load_sets_attributes(\n        self,\n        mock_enroll,\n        mock_headerinfo,\n        mock_profile,\n        mock_proxy,\n        mock_eab,\n        mock_load_config,\n    ):\n        # Simulate config_dic with needed structure\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"vault_url\": \"url\",\n            \"vault_path\": \"path\",\n            \"vault_role\": \"role\",\n            \"vault_token\": \"token\",\n            \"issuer_ref\": \"issuer\",\n            \"request_timeout\": \"30\",\n            \"cert_validity_days\": \"400\",\n            \"ca_bundle\": True,\n        }\n\n        mock_load_config.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(self.cahandler.vault_url, \"url\")\n        self.assertEqual(self.cahandler.vault_path, \"path\")\n        self.assertEqual(self.cahandler.vault_role, \"role\")\n        self.assertEqual(self.cahandler.vault_token, \"token\")\n        self.assertEqual(self.cahandler.issuer_ref, \"issuer\")\n        self.assertEqual(self.cahandler.request_timeout, 30)\n        self.assertEqual(self.cahandler.cert_validity_days, 400)\n        self.assertTrue(self.cahandler.ca_bundle)\n        self.assertEqual(self.cahandler.eab_profiling, True)\n        self.assertEqual(self.cahandler.eab_handler, \"handler\")\n        self.assertEqual(self.cahandler.proxy, {\"http\": \"proxy\"})\n        self.assertEqual(self.cahandler.profiles, {\"profile\": \"data\"})\n        self.assertTrue(self.cahandler.header_info_field)\n        self.assertTrue(self.cahandler.enrollment_config_log)\n        self.assertEqual(\n            self.cahandler.enrollment_config_log_skip_list, [\"skip1\", \"skip2\"]\n        )\n\n    @patch(\"examples.ca_handler.vault_ca_handler.load_config\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_eab_profile_load\",\n        return_value=(True, \"handler\"),\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_proxy_load\",\n        return_value={\"http\": \"proxy\"},\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_profile_load\",\n        return_value={\"profile\": \"data\"},\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_headerinfo_load\", return_value=True\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.config_enroll_config_log_load\",\n        return_value=(True, [\"skip1\", \"skip2\"]),\n    )\n    def test_019_config_load_sets_attributes(\n        self,\n        mock_enroll,\n        mock_headerinfo,\n        mock_profile,\n        mock_proxy,\n        mock_eab,\n        mock_load_config,\n    ):\n        # Simulate config_dic with needed structure\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\n            \"cert_validity_days\": \"aa\",\n            \"request_timeout\": \"aa\",\n        }\n\n        mock_load_config.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(self.cahandler.cert_validity_days, 365)\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse cert_validity_days invalid literal for int() with base 10: 'aa' parameter\",\n            lcm.output,\n        )\n        self.assertEqual(self.cahandler.request_timeout, 20)\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to parse request_timeout parameter: invalid literal for int() with base 10: 'aa'\",\n            lcm.output,\n        )\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.enrollment_config_log\", return_value=None\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=None,\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._csr_check\", return_value=None\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.csr_cn_lookup\", return_value=\"test-cn\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.build_pem_file\", return_value=\"pem-csr\"\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_url_recode\", return_value=\"recode-csr\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._api_post\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_encode\", return_value=\"encoded-cert\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.cert_pem2der\", return_value=\"der-cert\")\n    def test_020_enroll_success(\n        self,\n        mock_cert_pem2der,\n        mock_b64_encode,\n        mock_api_post,\n        mock_b64_url_recode,\n        mock_build_pem_file,\n        mock_csr_cn_lookup,\n        mock_csr_check,\n        mock_config_check,\n        mock_log,\n    ):\n        # Simulate successful API response\n        mock_api_post.return_value = (\n            200,\n            {\"data\": {\"certificate\": \"CERT\", \"ca_chain\": [\"CA1\", \"CA2\"]}},\n        )\n\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertIsNone(error)\n        self.assertIn(\"CERT\", cert_bundle)\n        self.assertIn(\"CA1\", cert_bundle)\n        self.assertIn(\"CA2\", cert_bundle)\n        self.assertEqual(cert_raw, \"encoded-cert\")\n        self.assertIsNone(poll_identifier)\n        mock_api_post.assert_called_once_with(\n            \"None/v1/None/sign/None\", {\"csr\": \"pem-csr\", \"common_name\": \"test-cn\"}\n        )\n        self.assertFalse(mock_log.called)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.enrollment_config_log\", return_value=None\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=None,\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._csr_check\", return_value=None\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.csr_cn_lookup\", return_value=\"test-cn\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.build_pem_file\", return_value=\"pem-csr\"\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_url_recode\", return_value=\"recode-csr\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._api_post\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_encode\", return_value=\"encoded-cert\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.cert_pem2der\", return_value=\"der-cert\")\n    def test_021_enroll_success(\n        self,\n        mock_cert_pem2der,\n        mock_b64_encode,\n        mock_api_post,\n        mock_b64_url_recode,\n        mock_build_pem_file,\n        mock_csr_cn_lookup,\n        mock_csr_check,\n        mock_config_check,\n        mock_log,\n    ):\n        # Simulate successful API response\n        mock_api_post.return_value = (\n            200,\n            {\"data\": {\"certificate\": \"CERT\", \"ca_chain\": [\"CA1\", \"CA2\"]}},\n        )\n        self.cahandler.enrollment_config_log = True\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertIsNone(error)\n        self.assertIn(\"CERT\", cert_bundle)\n        self.assertIn(\"CA1\", cert_bundle)\n        self.assertIn(\"CA2\", cert_bundle)\n        self.assertEqual(cert_raw, \"encoded-cert\")\n        self.assertIsNone(poll_identifier)\n        mock_api_post.assert_called_once_with(\n            \"None/v1/None/sign/None\", {\"csr\": \"pem-csr\", \"common_name\": \"test-cn\"}\n        )\n        self.assertTrue(mock_log.called)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=None,\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._csr_check\", return_value=None\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.csr_cn_lookup\", return_value=\"test-cn\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.build_pem_file\", return_value=\"pem-csr\"\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_url_recode\", return_value=\"recode-csr\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._api_post\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_encode\", return_value=\"encoded-cert\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.cert_pem2der\", return_value=\"der-cert\")\n    def test_022_enroll_success(\n        self,\n        mock_cert_pem2der,\n        mock_b64_encode,\n        mock_api_post,\n        mock_b64_url_recode,\n        mock_build_pem_file,\n        mock_csr_cn_lookup,\n        mock_csr_check,\n        mock_config_check,\n    ):\n        # Simulate successful API response\n        mock_api_post.return_value = (\n            200,\n            {\"data\": {\"certificate\": \"CERT\", \"ca_chain\": [\"CA1\", \"CA2\"]}},\n        )\n        self.cahandler.issuer_ref = \"test-issuer\"\n        self.cahandler.vault_path = \"path\"\n        self.cahandler.vault_url = \"url\"\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertIsNone(error)\n        self.assertIn(\"CERT\", cert_bundle)\n        self.assertIn(\"CA1\", cert_bundle)\n        self.assertIn(\"CA2\", cert_bundle)\n        self.assertEqual(cert_raw, \"encoded-cert\")\n        self.assertIsNone(poll_identifier)\n        mock_api_post.assert_called_once_with(\n            \"url/v1/path/issuer/test-issuer/sign/None\",\n            {\"csr\": \"pem-csr\", \"common_name\": \"test-cn\"},\n        )\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=\"config error\",\n    )\n    def test_023_enroll_config_error(self, mock_config_check):\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertEqual(error, \"config error\")\n        self.assertIsNone(cert_bundle)\n        self.assertIsNone(cert_raw)\n        self.assertIsNone(poll_identifier)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=None,\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._csr_check\",\n        return_value=\"csr error\",\n    )\n    def test_024_enroll_csr_error(self, mock_csr_check, mock_config_check):\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertEqual(error, \"csr error\")\n        self.assertIsNone(cert_bundle)\n        self.assertIsNone(cert_raw)\n        self.assertIsNone(poll_identifier)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._config_check\",\n        return_value=None,\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.CAhandler._csr_check\", return_value=None\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.csr_cn_lookup\", return_value=\"test-cn\")\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.build_pem_file\", return_value=\"pem-csr\"\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.b64_url_recode\", return_value=\"recode-csr\"\n    )\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._api_post\")\n    def test_025_enroll_api_error(\n        self,\n        mock_api_post,\n        mock_b64_url_recode,\n        mock_build_pem_file,\n        mock_csr_cn_lookup,\n        mock_csr_check,\n        mock_config_check,\n    ):\n        # Simulate failed API response\n        mock_api_post.return_value = (400, {\"errors\": [\"api error\"]})\n\n        error, cert_bundle, cert_raw, poll_identifier = self.cahandler.enroll(\n            \"dummy-csr\"\n        )\n        self.assertIsNotNone(error)\n        self.assertIn(\"api error\", error)\n        self.assertIsNone(cert_bundle)\n        self.assertIsNone(cert_raw)\n        self.assertIsNone(poll_identifier)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.enrollment_config_log\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.eab_profile_revocation_check\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.cert_serial_get\",\n        return_value=\"abcdef1234\",\n    )\n    def test_026_revoke_success(self, mock_cert_serial_get, mock_eab, mock_log):\n        self.cahandler._api_post = MagicMock(return_value=(200, {}))\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_called_once()\n        self.assertEqual(code, 200)\n        self.assertIsNone(message)\n        self.assertIsNone(detail)\n        self.assertTrue(mock_cert_serial_get.called)\n        self.assertFalse(mock_eab.called)\n        self.assertFalse(mock_log.called)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.enrollment_config_log\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.eab_profile_revocation_check\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.cert_serial_get\",\n        return_value=\"abcdef1234\",\n    )\n    def test_027_revoke_success(self, mock_cert_serial_get, mock_eab, mock_log):\n        self.cahandler._api_post = MagicMock(return_value=(200, {}))\n        self.cahandler.eab_profiling = True\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_called_once()\n        self.assertEqual(code, 200)\n        self.assertIsNone(message)\n        self.assertIsNone(detail)\n        self.assertTrue(mock_cert_serial_get.called)\n        self.assertTrue(mock_eab.called)\n        self.assertFalse(mock_log.called)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.enrollment_config_log\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.eab_profile_revocation_check\",\n    )\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.cert_serial_get\",\n        return_value=\"abcdef1234\",\n    )\n    def test_028_revoke_success(self, mock_cert_serial_get, mock_eab, mock_log):\n        self.cahandler._api_post = MagicMock(return_value=(200, {}))\n        self.cahandler.enrollment_config_log = True\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_called_once()\n        self.assertEqual(code, 200)\n        self.assertIsNone(message)\n        self.assertIsNone(detail)\n        self.assertTrue(mock_cert_serial_get.called)\n        self.assertFalse(mock_eab.called)\n        self.assertTrue(mock_log.called)\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.cert_serial_get\",\n        return_value=\"abcdef1234\",\n    )\n    def test_029_revoke_api_error(self, mock_cert_serial_get):\n        self.cahandler._api_post = MagicMock(return_value=(400, {\"errors\": [\"fail\"]}))\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_called_once()\n        self.assertEqual(code, 400)\n        self.assertFalse(message)\n        self.assertEqual(detail, '[\"fail\"]')\n\n    @patch(\n        \"examples.ca_handler.vault_ca_handler.cert_serial_get\",\n        return_value=\"abcdef1234\",\n    )\n    def test_030_revoke_api_error(self, mock_cert_serial_get):\n        self.cahandler._api_post = MagicMock(return_value=(400, {\"foo\": [\"fail\"]}))\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_called_once()\n        self.assertEqual(code, 400)\n        self.assertFalse(message)\n        self.assertEqual('{\"foo\": [\"fail\"]}', detail)\n\n    @patch(\"examples.ca_handler.vault_ca_handler.cert_serial_get\", return_value=None)\n    def test_031_revoke_no_serial(self, mock_cert_serial_get):\n        self.cahandler._api_post = MagicMock()\n        code, message, detail = self.cahandler.revoke(\"dummy_cert\")\n        self.cahandler._api_post.assert_not_called()\n        self.assertEqual(code, 500)\n        self.assertIsNone(message)\n        self.assertEqual(detail, \"Failed to parse certificate serial\")\n\n    @patch(\"examples.ca_handler.vault_ca_handler.CAhandler._config_check\")\n    def test_032_handler_check(self, mock_config_check):\n        mock_config_check.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.cahandler.handler_check())\n\n    @patch(\"examples.ca_handler.vault_ca_handler.eab_profile_header_info_check\")\n    def test_033_csr_check(self, mock_hic):\n        mock_hic.return_value = \"mock_hlc\"\n        self.assertEqual(\"mock_hlc\", self.cahandler._csr_check(\"dummy-csr\"))\n        self.assertTrue(mock_hic.called)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_wsgi_acme2certifier.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0415, R0904, R0913, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, mock_open, Mock, MagicMock\nfrom io import StringIO\n\n# from OpenSSL import crypto\nimport shutil\nimport configparser\nimport json\n\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\nclass FakeDBStore(object):\n    \"\"\"face DBStore class needed for mocking\"\"\"\n\n    # pylint: disable=W0107, R0903\n    pass\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    @patch.dict(\"os.environ\", {\"ACME_SRV_CONFIGFILE\": \"ACME_SRV_CONFIGFILE\"})\n    def setUp(self):\n        \"\"\"setup unittest with fresh wsgi module state\"\"\"\n        import logging\n        import importlib\n        import examples.acme2certifier_wsgi\n\n        importlib.reload(examples.acme2certifier_wsgi)\n        from examples.acme2certifier_wsgi import (\n            create_header,\n            get_request_body,\n            acct,\n            acmechallenge_serve,\n            authz,\n            handle_exception,\n            newaccount,\n            directory,\n            cert,\n            chall,\n            neworders,\n            newnonce,\n            order,\n            revokecert,\n            trigger,\n            not_found,\n            application,\n            get_handler_cls,\n            housekeeping,\n            renewalinfo,\n        )\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.acct = acct\n        self.create_header = create_header\n        self.get_request_body = get_request_body\n        self.acmechallenge_serve = acmechallenge_serve\n        self.handle_exception = handle_exception\n        self.housekeeping = housekeeping\n        self.authz = authz\n        self.newaccount = newaccount\n        self.neworders = neworders\n        self.newnonce = newnonce\n        self.directory = directory\n        self.cert = cert\n        self.chall = chall\n        self.order = order\n        self.revokecert = revokecert\n        self.trigger = trigger\n        self.not_found = not_found\n        self.application = application\n        self.get_handler_cls = get_handler_cls\n        self.renewalinfo = renewalinfo\n        self.start_response = MagicMock()\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        pass\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002_create_header(self):\n        \"\"\"create header\"\"\"\n        response_dic = {}\n        result = [(\"Content-Type\", \"application/json\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_003_create_header(self):\n        \"\"\"create header unknown response_dic\"\"\"\n        response_dic = {\"foo\": \"bar\"}\n        result = [(\"Content-Type\", \"application/json\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_004_create_header(self):\n        \"\"\"create header response_dic with header\"\"\"\n        response_dic = {\"header\": {\"foo\": \"bar\"}}\n        result = [(\"Content-Type\", \"application/json\"), (\"foo\", \"bar\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_005_create_header(self):\n        \"\"\"create header response_dic with header and code = 200\"\"\"\n        response_dic = {\"code\": 200, \"header\": {\"foo\": \"bar\"}}\n        result = [(\"Content-Type\", \"application/json\"), (\"foo\", \"bar\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_006_create_header(self):\n        \"\"\"create header response_dic with header and code = 201\"\"\"\n        response_dic = {\"code\": 201, \"header\": {\"foo\": \"bar\"}}\n        result = [(\"Content-Type\", \"application/json\"), (\"foo\", \"bar\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_007_create_header(self):\n        \"\"\"create header response_dic with header and code = 400\"\"\"\n        response_dic = {\"code\": 400, \"header\": {\"foo\": \"bar\"}}\n        result = [(\"Content-Type\", \"application/problem+json\"), (\"foo\", \"bar\")]\n        self.assertEqual(result, self.create_header(response_dic))\n\n    def test_008_create_header(self):\n        \"\"\"create header response_dic with header and add_json_header false\"\"\"\n        response_dic = {\"code\": 400, \"header\": {\"foo\": \"bar\"}}\n        result = [(\"foo\", \"bar\")]\n        self.assertEqual(\n            result, self.create_header(response_dic, add_json_header=False)\n        )\n\n    def test_009_create_header(self):\n        \"\"\"create header response_dic with header and code = 400 and add_json_header true\"\"\"\n        response_dic = {\"code\": 400, \"header\": {\"foo\": \"bar\"}}\n        result = [(\"Content-Type\", \"application/problem+json\"), (\"foo\", \"bar\")]\n        self.assertEqual(result, self.create_header(response_dic, add_json_header=True))\n\n    def test_010_get_request_body(self):\n        \"\"\"get_request_body with empty environment\"\"\"\n        environ = {}\n        self.assertFalse(self.get_request_body(environ))\n\n    def test_011_get_request_body(self):\n        \"\"\"get_request_body with environment data but no CONTENT_LENGTH specification\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\")}\n        self.assertFalse(self.get_request_body(environ))\n\n    def test_012_get_request_body(self):\n        \"\"\"get_request_body with environment data but CONTENT_LENGTH specification 0 (read full content)\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\"), \"CONTENT_LENGTH\": 0}\n        self.assertFalse(self.get_request_body(environ))\n\n    def test_013_get_request_body(self):\n        \"\"\"get_request_body with environment data content length lower than string length\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\"), \"CONTENT_LENGTH\": 2}\n        self.assertEqual(\"fo\", self.get_request_body(environ))\n\n    def test_014_get_request_body(self):\n        \"\"\"get_request_body with environment data content length lower than string length\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\"), \"CONTENT_LENGTH\": 3}\n        self.assertEqual(\"foo\", self.get_request_body(environ))\n\n    def test_015_get_request_body(self):\n        \"\"\"get_request_body with environment data content length lower than string length\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\"), \"CONTENT_LENGTH\": 10}\n        self.assertEqual(\"foo\", self.get_request_body(environ))\n\n    def test_016_get_request_body(self):\n        \"\"\"get_request_body with environment data content length lower than string length\"\"\"\n        environ = {\"wsgi.input\": StringIO(\"\"\"foo\"\"\"), \"CONTENT_LENGTH\": \"aaa\"}\n        self.assertFalse(self.get_request_body(environ))\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"acme_srv.account.Account.parse\")\n    def test_017_acct(self, mock_parse, mock_body, mock_url, mock_header):\n        \"\"\"acct\"\"\"\n        environ = \"environ\"\n        mock_parse.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.acct(environ, Mock()))\n        self.assertTrue(mock_body.called)\n        self.assertTrue(mock_parse.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n\n    @patch(\"acme_srv.acmechallenge.Acmechallenge.lookup\")\n    def test_018_acmechallenge_serve(self, mock_lookup):\n        \"\"\"acmechallenge_serve\"\"\"\n        environ = {\"PATH_INFO\": \"PATH_INFO\", \"REMOTE_ADDR\": \"REMOTE_ADDR\"}\n        mock_lookup.return_value = \"foo\"\n        self.assertEqual([b\"foo\"], self.acmechallenge_serve(environ, Mock()))\n\n    @patch(\"acme_srv.acmechallenge.Acmechallenge.lookup\")\n    def test_019_acmechallenge_serve(self, mock_lookup):\n        \"\"\"acmechallenge_serve no key_authorization\"\"\"\n        environ = {\"PATH_INFO\": \"PATH_INFO\", \"REMOTE_ADDR\": \"REMOTE_ADDR\"}\n        mock_lookup.return_value = None\n        self.assertEqual([b\"NOT FOUND\"], self.acmechallenge_serve(environ, Mock()))\n\n    @patch(\"acme_srv.authorization.Authorization.new_post\")\n    @patch(\"acme_srv.authorization.Authorization.new_get\")\n    def test_020_authz(self, mock_get, mock_post):\n        \"\"\"authz neither get or post\"\"\"\n        environ = {\"foo\": \"bar\", \"wsgi.input\": StringIO(\"\"\"foo\"\"\")}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.authz(environ, Mock()),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.authorization.Authorization.new_post\")\n    @patch(\"acme_srv.authorization.Authorization.new_get\")\n    def test_021_authz(self, mock_get, mock_post, mock_header):\n        \"\"\"authz get\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n            \"wsgi.input\": StringIO(\"\"\"foo\"\"\"),\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.authz(environ, Mock()))\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.authorization.Authorization.new_post\")\n    @patch(\"acme_srv.authorization.Authorization.new_get\")\n    def test_022_authz(self, mock_get, mock_post, mock_header):\n        \"\"\"authz post no content length\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n            \"wsgi.input\": StringIO(\"\"\"foo\"\"\"),\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.authz(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.authorization.Authorization.new_post\")\n    @patch(\"acme_srv.authorization.Authorization.new_get\")\n    def test_023_authz(self, mock_get, mock_post, mock_header):\n        \"\"\"authz post content length int\"\"\"\n        environ = {\n            \"CONTENT_LENGTH\": 2,\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n            \"wsgi.input\": StringIO(\"\"\"foo\"\"\"),\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.authz(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.authorization.Authorization.new_post\")\n    @patch(\"acme_srv.authorization.Authorization.new_get\")\n    def test_024_authz(self, mock_get, mock_post, mock_header):\n        \"\"\"authz post no content length string\"\"\"\n        environ = {\n            \"CONTENT_LENGTH\": \"A\",\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n            \"wsgi.input\": StringIO(\"\"\"foo\"\"\"),\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.authz(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n\n    @patch(\"sys.__excepthook__\")\n    def test_025_handle_exception_keyboard_interrupt(self, mock_excepthook):\n        \"\"\"test handle_exception with KeyboardInterrupt - should call sys.__excepthook__\"\"\"\n        exc_type = KeyboardInterrupt\n        exc_value = KeyboardInterrupt(\"Test keyboard interrupt\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that sys.__excepthook__ was called with correct parameters\n        mock_excepthook.assert_called_once_with(exc_type, exc_value, exc_traceback)\n        # Verify function returned None (early return)\n        self.assertIsNone(result)\n\n    @patch(\"sys.__excepthook__\")\n    def test_026_handle_exception_keyboard_interrupt_subclass(self, mock_excepthook):\n        \"\"\"test handle_exception with KeyboardInterrupt subclass\"\"\"\n\n        class CustomKeyboardInterrupt(KeyboardInterrupt):\n            pass\n\n        exc_type = CustomKeyboardInterrupt\n        exc_value = CustomKeyboardInterrupt(\"Custom keyboard interrupt\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that sys.__excepthook__ was called\n        mock_excepthook.assert_called_once_with(exc_type, exc_value, exc_traceback)\n        self.assertIsNone(result)\n\n    @patch(\"examples.acme2certifier_wsgi.LOGGER\")\n    @patch(\"sys.__excepthook__\")\n    def test_027_handle_exception_regular_exception(self, mock_excepthook, mock_logger):\n        \"\"\"test handle_exception with regular exception - should log via LOGGER\"\"\"\n        exc_type = ValueError\n        exc_value = ValueError(\"Test value error\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify that sys.__excepthook__ was NOT called\n        mock_excepthook.assert_not_called()\n\n        # Verify that LOGGER.exception was called with correct parameters\n        mock_logger.exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        self.assertIsNone(result)\n\n    @patch(\"examples.acme2certifier_wsgi.LOGGER\")\n    @patch(\"sys.__excepthook__\")\n    def test_028_handle_exception_runtime_error(self, mock_excepthook, mock_logger):\n        \"\"\"test handle_exception with RuntimeError\"\"\"\n        exc_type = RuntimeError\n        exc_value = RuntimeError(\"Test runtime error\")\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        mock_excepthook.assert_not_called()\n        mock_logger.exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        self.assertIsNone(result)\n\n    @patch(\"examples.acme2certifier_wsgi.LOGGER\")\n    @patch(\"sys.__excepthook__\")\n    def test_029_handle_exception_system_exit(self, mock_excepthook, mock_logger):\n        \"\"\"test handle_exception with SystemExit - should log, not call excepthook\"\"\"\n        exc_type = SystemExit\n        exc_value = SystemExit(1)\n        exc_traceback = Mock()\n\n        result = self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # SystemExit is not a subclass of KeyboardInterrupt, so should log\n        mock_excepthook.assert_not_called()\n        mock_logger.exception.assert_called_once_with(\n            \"Uncaught exception\", exc_info=(exc_type, exc_value, exc_traceback)\n        )\n        self.assertIsNone(result)\n\n    @patch(\"examples.acme2certifier_wsgi.LOGGER\")\n    @patch(\"sys.__excepthook__\")\n    def test_030_handle_exception_exc_info_tuple_format(\n        self, mock_excepthook, mock_logger\n    ):\n        \"\"\"test that exc_info is passed as correct tuple format\"\"\"\n        exc_type = RuntimeError\n        exc_value = RuntimeError(\"Test runtime error\")\n        exc_traceback = Mock()\n\n        self.handle_exception(exc_type, exc_value, exc_traceback)\n\n        # Verify the exc_info parameter is passed as a tuple\n        mock_logger.exception.assert_called_once()\n        call_args = mock_logger.exception.call_args\n\n        # Check the exc_info keyword argument\n        self.assertIn(\"exc_info\", call_args.kwargs)\n        exc_info_tuple = call_args.kwargs[\"exc_info\"]\n\n        # Verify it's a tuple with 3 elements\n        self.assertIsInstance(exc_info_tuple, tuple)\n        self.assertEqual(len(exc_info_tuple), 3)\n        self.assertEqual(exc_info_tuple[0], exc_type)\n        self.assertEqual(exc_info_tuple[1], exc_value)\n        self.assertEqual(exc_info_tuple[2], exc_traceback)\n\n    def test_031_handle_exception_issubclass_check_various_types(self):\n        \"\"\"test that issubclass check works correctly for various exception types\"\"\"\n        test_cases = [\n            (ValueError, False),\n            (RuntimeError, False),\n            (AttributeError, False),\n            (TypeError, False),\n            (KeyError, False),\n            (IndexError, False),\n            (ImportError, False),\n            (OSError, False),\n            (SystemExit, False),\n            (BaseException, False),\n            (KeyboardInterrupt, True),\n        ]\n\n        for exc_type, should_call_excepthook in test_cases:\n            with self.subTest(exc_type=exc_type):\n                with patch(\"examples.acme2certifier_wsgi.LOGGER\") as mock_logger:\n                    with patch(\"sys.__excepthook__\") as mock_excepthook:\n                        exc_value = exc_type(\"Test exception\")\n                        exc_traceback = Mock()\n\n                        result = self.handle_exception(\n                            exc_type, exc_value, exc_traceback\n                        )\n\n                        if should_call_excepthook:\n                            mock_excepthook.assert_called_once()\n                            mock_logger.exception.assert_not_called()\n                        else:\n                            mock_excepthook.assert_not_called()\n                            mock_logger.exception.assert_called_once()\n\n                        self.assertIsNone(result)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"acme_srv.account.Account.new\")\n    def test_032_newaccount(self, mock_new, mock_body, mock_url, mock_header):\n        \"\"\"new account - post\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_new.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.newaccount(environ, Mock()))\n        self.assertTrue(mock_body.called)\n        self.assertTrue(mock_new.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"acme_srv.account.Account.new\")\n    def test_033_newaccount(self, mock_new, mock_body, mock_url, mock_header):\n        \"\"\"newaccount - wrong request method\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"WRONG\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_new.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.newaccount(environ, Mock()),\n        )\n        self.assertFalse(mock_body.called)\n        self.assertFalse(mock_new.called)\n        self.assertFalse(mock_url.called)\n        self.assertFalse(mock_header.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"acme_srv.directory.Directory.directory_get\")\n    def test_034_directory(self, mock_get, mock_body, mock_url, mock_header):\n        \"\"\"newaccount - all ok\"\"\"\n        environ = {\"REMOTE_ADDR\": \"REMOTE_ADDR\", \"PATH_INFO\": \"PATH_INFO\"}\n        mock_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [b'{\"code\": 200, \"data\": \"data\"}'], self.directory(environ, Mock())\n        )\n        self.assertFalse(mock_body.called)\n        self.assertTrue(mock_get.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"acme_srv.directory.Directory.directory_get\")\n    def test_035_directory(self, mock_get, mock_body, mock_url, mock_header):\n        \"\"\"newaccount - directory.get throws an error\"\"\"\n        environ = {\"REMOTE_ADDR\": \"REMOTE_ADDR\", \"PATH_INFO\": \"PATH_INFO\"}\n        mock_get.return_value = {\"code\": 500, \"error\": \"error\"}\n        self.assertEqual(\n            [b'{\"status\": 403, \"message\": \"Forbidden\", \"detail\": \"error\"}'],\n            self.directory(environ, Mock()),\n        )\n        self.assertFalse(mock_body.called)\n        self.assertTrue(mock_get.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.certificate.Certificate.new_post\")\n    @patch(\"acme_srv.certificate.Certificate.new_get\")\n    def test_036_cert(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"cert unknown request method\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        # mock_post.return_value = {'code': 200, 'data': 'data'}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.cert(environ, Mock()),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.certificate.Certificate.new_post\")\n    @patch(\"acme_srv.certificate.Certificate.new_get\")\n    def test_037_cert(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"cert GET request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([\"data\"], self.cert(environ, Mock()))\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.certificate.Certificate.new_post\")\n    @patch(\"acme_srv.certificate.Certificate.new_get\")\n    def test_038_cert(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"cert POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b\"data\"], self.cert(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.challenge.Challenge.parse\")\n    @patch(\"acme_srv.challenge.Challenge.get\")\n    def test_039_chall(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"chall unknown request method\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        # mock_post.return_value = {'code': 200, 'data': 'data'}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.chall(environ, Mock()),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.challenge.Challenge.parse\")\n    @patch(\"acme_srv.challenge.Challenge.get\")\n    def test_040_chall(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"chall GET request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.chall(environ, Mock()))\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.challenge.Challenge.parse\")\n    @patch(\"acme_srv.challenge.Challenge.get\")\n    def test_041_chall(self, mock_get, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"chall POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.chall(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.order.Order.new\")\n    def test_042_order(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.neworders(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.order.Order.new\")\n    def test_043_order(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order unknown request type\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.neworders(environ, Mock()),\n        )\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_044_nnonce(self, mock_gen, mock_header, mock_body):\n        \"\"\"chall GET request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_gen.return_value = \"foo\"\n        self.assertFalse(self.newnonce(environ, Mock()))\n        self.assertTrue(mock_gen.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_045_nnonce(self, mock_gen):\n        \"\"\"chall HEAD request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"HEAD\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_gen.return_value = \"foo\"\n        self.assertFalse(self.newnonce(environ, Mock()))\n        self.assertTrue(mock_gen.called)\n\n    @patch(\"acme_srv.nonce.Nonce.generate_and_add\")\n    def test_046_nnonce(self, mock_gen):\n        \"\"\"chall HEAD request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_gen.return_value = \"foo\"\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected HEAD or GET.\"}'\n            ],\n            self.newnonce(environ, Mock()),\n        )\n        self.assertFalse(mock_gen.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.order.Order.parse\")\n    def test_047_order(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.order(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.order.Order.new\")\n    def test_048_order(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order unknown request type\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.order(environ, Mock()),\n        )\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.certificate.Certificate.revoke\")\n    def test_049_revokecert(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.revokecert(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.certificate.Certificate.revoke\")\n    def test_050_revokecert(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200}\n        self.assertFalse(self.revokecert(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.order.Order.new\")\n    def test_051_revokecert(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"order unknown request type\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.revokecert(environ, Mock()),\n        )\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.trigger.Trigger.parse\")\n    def test_052_trigger(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"trigger POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.trigger(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.trigger.Trigger.parse\")\n    def test_053_trigger(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"trigger POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200}\n        self.assertFalse(self.trigger(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.trigger.Trigger.parse\")\n    def test_054_trigger(self, mock_post, mock_url, mock_header, mock_body):\n        \"\"\"trigger unknown request type\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.trigger(environ, Mock()),\n        )\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    def test_055_notfound(self):\n        \"\"\"notfound\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        self.assertEqual(\n            [b'{\"status\": 404, \"message\": \"Not Found\", \"detail\": \"Not Found\"}'],\n            self.not_found(environ, Mock()),\n        )\n\n    @patch(\"examples.acme2certifier_wsgi.CONFIG\", {\"Directory\": {\"url_prefix\": \"\"}})\n    def test_056_application(self):\n        \"\"\"Test redirect to /directory when root URL is accessed.\"\"\"\n        self.environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"PATH_INFO\": \"\",\n            \"REMOTE_ADDR\": \"127.0.0.1\",\n        }\n        self.start_response = MagicMock()\n        self.environ[\"PATH_INFO\"] = \"/\"\n        response = self.application(self.environ, self.start_response)\n        self.start_response.assert_called_with(\n            \"302 Found\", [(\"Location\", \"/directory\")]\n        )\n        self.assertEqual(response, [])\n\n    @patch(\"examples.acme2certifier_wsgi.CONFIG\", {\"Directory\": {\"url_prefix\": \"\"}})\n    @patch(\n        \"acme_srv.directory.DirectoryRepository.get_db_version\",\n        return_value=(\"1.0\", \"script_name\"),\n    )\n    @patch(\"acme_srv.directory.Directory.directory_get\")\n    def test_057_application(self, mock_directory_get, mock_get_db_version):\n        \"\"\"Test accessing the /acme/directory endpoint.\"\"\"\n        mock_directory_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"PATH_INFO\": \"\",\n            \"REMOTE_ADDR\": \"127.0.0.1\",\n            \"PATH_INFO\": \"/acme/directory\",\n        }\n        response = self.application(self.environ, self.start_response)\n        self.start_response.assert_called()\n        self.assertIsInstance(response, list)\n\n    @patch(\"examples.acme2certifier_wsgi.CONFIG\", {\"Directory\": {\"url_prefix\": \"\"}})\n    def test_058_application(self):\n        \"\"\"Test accessing the /acme/acct endpoint.\"\"\"\n        self.environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"PATH_INFO\": \"\",\n            \"REMOTE_ADDR\": \"127.0.0.1\",\n            \"PATH_INFO\": \"/acme/acct\",\n        }\n        with patch(\"examples.acme2certifier_wsgi.acct\", self.acct):\n            response = self.application(self.environ, self.start_response)\n            self.start_response.assert_called()\n            self.assertIsInstance(response, list)\n\n    @patch(\"examples.acme2certifier_wsgi.CONFIG\", {\"Directory\": {\"url_prefix\": \"\"}})\n    def test_059_application(self):\n        \"\"\"Test accessing the /acme/newaccount endpoint.\"\"\"\n        self.environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"PATH_INFO\": \"\",\n            \"REMOTE_ADDR\": \"127.0.0.1\",\n            \"PATH_INFO\": \"/acme/newaccount\",\n        }\n        with patch(\"examples.acme2certifier_wsgi.newaccount\", self.newaccount):\n            response = self.application(self.environ, self.start_response)\n            self.start_response.assert_called()\n            self.assertIsInstance(response, list)\n\n    @patch(\"examples.acme2certifier_wsgi.CONFIG\", {\"Directory\": {\"url_prefix\": \"\"}})\n    def test_060_application(self):\n        \"\"\"Test accessing an unknown endpoint.\"\"\"\n        self.environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"PATH_INFO\": \"\",\n            \"REMOTE_ADDR\": \"127.0.0.1\",\n            \"PATH_INFO\": \"/unknown/path\",\n        }\n        with patch(\"examples.acme2certifier_wsgi.not_found\", self.not_found):\n            response = self.application(self.environ, self.start_response)\n            self.start_response.assert_called_with(\n                \"404 NOT FOUND\", [(\"Content-Type\", \"text/plain\")]\n            )\n            self.assertIsInstance(response, list)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.parse\")\n    def test_061_housekeeping(self, mock_post, mock_header, mock_body):\n        \"\"\"housekeeping POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.housekeeping(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.parse\")\n    def test_062_housekeeping(self, mock_post, mock_header, mock_body):\n        \"\"\"housekeeping POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.housekeeping(environ, Mock()),\n        )\n        self.assertFalse(mock_post.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"acme_srv.housekeeping.Housekeeping.parse\")\n    def test_063_housekeeping(self, mock_post, mock_header, mock_body):\n        \"\"\"housekeeping POST request without data\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"foo\": \"bar\"}\n        self.assertFalse(self.housekeeping(environ, Mock()))\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.update\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.get\")\n    def test_064_renewalinfo(\n        self, mock_get, mock_post, mock_url, mock_header, mock_body\n    ):\n        \"\"\"renewalinfo unknown request method\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"UNK\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        # mock_post.return_value = {'code': 200, 'data': 'data'}\n        self.assertEqual(\n            [\n                b'{\"status\": 405, \"message\": \"Method Not Allowed\", \"detail\": \"Wrong request type. Expected POST.\"}'\n            ],\n            self.renewalinfo(environ, Mock()),\n        )\n        self.assertFalse(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertFalse(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.update\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.get\")\n    def test_065_renewalinfo(\n        self, mock_get, mock_post, mock_url, mock_header, mock_body\n    ):\n        \"\"\"renewalinfo GET request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_get.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([b'\"data\"'], self.renewalinfo(environ, Mock()))\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.update\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.get\")\n    def test_066_renewalinfo(\n        self, mock_get, mock_post, mock_url, mock_header, mock_body\n    ):\n        \"\"\"renewalinfo GET request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"GET\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_get.return_value = {\"code\": 200}\n        self.assertFalse(self.renewalinfo(environ, Mock()))\n        self.assertTrue(mock_get.called)\n        self.assertFalse(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertFalse(mock_body.called)\n\n    @patch(\"examples.acme2certifier_wsgi.get_request_body\")\n    @patch(\"examples.acme2certifier_wsgi.create_header\")\n    @patch(\"examples.acme2certifier_wsgi.get_url\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.update\")\n    @patch(\"acme_srv.renewalinfo.Renewalinfo.get\")\n    def test_067_renewalinfot(\n        self, mock_get, mock_post, mock_url, mock_header, mock_body\n    ):\n        \"\"\"renewalinfo POST request\"\"\"\n        environ = {\n            \"REQUEST_METHOD\": \"POST\",\n            \"REMOTE_ADDR\": \"REMOTE_ADDR\",\n            \"PATH_INFO\": \"PATH_INFO\",\n        }\n        mock_header.return_value = {\"header\": \"foo\"}\n        mock_post.return_value = {\"code\": 200, \"data\": \"data\"}\n        self.assertEqual([], self.renewalinfo(environ, Mock()))\n        self.assertFalse(mock_get.called)\n        self.assertTrue(mock_post.called)\n        self.assertTrue(mock_url.called)\n        self.assertTrue(mock_header.called)\n        self.assertTrue(mock_body.called)\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_wsgi_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for acme2certifier\"\"\"\nimport unittest\nimport sys\nimport os\n\ntry:\n    from mock import patch, MagicMock, Mock\nexcept ImportError:\n    from unittest.mock import patch, MagicMock, Mock\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\ndef dict_from_row(row):\n    \"\"\"small helper to convert a select list into a dictionary\"\"\"\n    return dict(zip(row.keys(), row))\n\n\ndef _cleanup(dir_path):\n    \"\"\"cleanup function\"\"\"\n    # remove old db\n    if os.path.exists(dir_path + \"/acme_test.db\"):\n        try:\n            os.remove(dir_path + \"/acme_test.db\")\n        except:\n            print(\"failed removal\")\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        # from acme_srv.wsgi_handler import DBstore\n        from examples.db_handler.wsgi_handler import DBstore, initialize\n        from acme_srv.version import __dbversion__\n        import logging\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n        self.dbstore = DBstore(False, self.logger, self.dir_path + \"/acme_test.db\")\n        self.initialize = initialize\n        self.dbversion = __dbversion__\n        _cleanup(self.dir_path)\n        self.dbstore._db_create()\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        _cleanup(self.dir_path)\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002_nonce_add(self):\n        \"\"\"test DBstore.nonce_add() method\"\"\"\n        self.assertEqual(1, self.dbstore.nonce_add(\"aaa\"))\n\n    def test_003_nonce_add_2(self):\n        \"\"\"test DBstore.nonce_add() method\"\"\"\n        self.dbstore.nonce_add(\"aaa\")\n        self.assertEqual(2, self.dbstore.nonce_add(\"bbb\"))\n\n    def test_004_nonce_check_1(self):\n        \"\"\"test DBstore.nonce_check() method\"\"\"\n        self.dbstore.nonce_add(\"aaa\")\n        self.assertTrue(self.dbstore.nonce_check(\"aaa\"))\n\n    def test_005_nonce_check_2(self):\n        \"\"\"test DBstore.nonce_check() method\"\"\"\n        self.dbstore.nonce_add(\"aaa\")\n        self.dbstore.nonce_add(\"bbb\")\n        self.assertTrue(self.dbstore.nonce_check(\"bbb\"))\n\n    def test_006_nonce_check_3(self):\n        \"\"\"test DBstore.nonce_check() method for a non existing entry\"\"\"\n        self.assertFalse(self.dbstore.nonce_check(\"ccc\"))\n\n    def test_007_nonce_delete(self):\n        \"\"\"test DBstore.nonce_delete() method\"\"\"\n        self.dbstore.nonce_add(\"bbb\")\n        self.assertEqual(None, self.dbstore.nonce_delete(\"bbb\"))\n\n    def test_008_nonce_delete_check(self):\n        \"\"\"test DBstore.nonce_delete() method for deleted entry\"\"\"\n        self.assertFalse(self.dbstore.nonce_check(\"bbb\"))\n\n    def test_009_accout_add(self):\n        \"\"\"test DBstore.account_add() method for a new entry without eab_kid\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.assertEqual((\"name1\", True), self.dbstore.account_add(data_dic))\n\n    def test_010_accout_add(self):\n        \"\"\"test DBstore.account_add() method for a new entry including eab_kid\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n            \"eab_kid\": \"eab_kid1\",\n        }\n        self.assertEqual((\"name1\", True), self.dbstore.account_add(data_dic))\n\n    def test_011_accout_add(self):\n        \"\"\"test DBstore.account_add() method for a new entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n\n    def test_012_accout_add(self):\n        \"\"\"test DBstore.account_add() method for an new entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg3\",\n            \"jwk\": \"jwk3\",\n            \"contact\": \"contact3\",\n            \"name\": \"name3\",\n        }\n        self.assertEqual((\"name3\", True), self.dbstore.account_add(data_dic))\n\n    def test_013_accout_add(self):\n        \"\"\"test DBstore.account_add() method for an existing entry (jwk already exists)\"\"\"\n        data_dic = {\n            \"alg\": \"alg3\",\n            \"jwk\": \"jwk3\",\n            \"contact\": \"contact3\",\n            \"name\": \"name3\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg4\",\n            \"jwk\": \"jwk3\",\n            \"contact\": \"contact4\",\n            \"name\": \"name4\",\n        }\n        self.assertEqual((\"name3\", False), self.dbstore.account_add(data_dic))\n\n    def test_014_accout_add(self):\n        \"\"\"test DBstore.account_add() method for an existing entry (jwk already exists) which has an eab-kid\"\"\"\n        data_dic = {\n            \"alg\": \"alg3\",\n            \"jwk\": \"jwk3\",\n            \"contact\": \"contact3\",\n            \"name\": \"name3\",\n            \"eab_kid\": \"eab_kid3\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg4\",\n            \"jwk\": \"jwk3\",\n            \"contact\": \"contact4\",\n            \"name\": \"name4\",\n        }\n        self.assertEqual((\"name3\", False), self.dbstore.account_add(data_dic))\n\n    def test_015_accout_search_alg(self):\n        \"\"\"test DBstore.account_seach() method for alg field\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"contact2\"), self.dbstore._account_search(\"alg\", \"alg2\"))\n\n    def test_016_accout_search_alg(self):\n        \"\"\"test DBstore.account_seach() method for alg field including eab_kid\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n            \"eab_kid\": \"eab_kid2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"contact2\"), self.dbstore._account_search(\"alg\", \"alg2\"))\n        self.assertIn((\"eab_kid2\"), self.dbstore._account_search(\"alg\", \"alg2\"))\n\n    def test_017_accout_search_jwk(self):\n        \"\"\"test DBstore.account_seach() method for jwk\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn(\n            (\"contact1\"),\n            self.dbstore._account_search(\"jwk\", '{\"key11\": \"val11\", \"key12\": \"val12\"}'),\n        )\n\n    def test_018_accout_search_jwk(self):\n        \"\"\"test DBstore.account_seach() method for jwk field\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"contact2\"), self.dbstore._account_search(\"jwk\", \"jwk2\"))\n\n    def test_019_accout_search_contact(self):\n        \"\"\"test DBstore.account_seach() method for eab_kid2 field\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"jwk2\"), self.dbstore._account_search(\"contact\", \"contact2\"))\n\n    def test_020_accout_search_contact(self):\n        \"\"\"test DBstore.account_seach() method for contact field\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n            \"eab_kid\": \"eab_kid2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"jwk2\"), self.dbstore._account_search(\"contact\", \"contact2\"))\n        self.assertIn((\"eab_kid2\"), self.dbstore._account_search(\"contact\", \"contact2\"))\n\n    def test_021_accout_search_eab(self):\n        \"\"\"test DBstore.account_seach() method for eab field\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n            \"eab_kid\": \"eab_kid2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"jwk2\"), self.dbstore._account_search(\"eab_kid\", \"eab_kid2\"))\n        self.assertIn((\"eab_kid2\"), self.dbstore._account_search(\"eab_kid\", \"eab_kid2\"))\n\n    def test_022_accout_search_exponent(self):\n        \"\"\"test DBstore.account_seach() method for alg field\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertIn((\"name1\"), self.dbstore._account_search(\"name\", \"name1\"))\n\n    def test_023_jkw_load(self):\n        \"\"\"test DBstore.jwk_load() for an existing key\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertEqual(\n            {\"alg\": \"alg1\", \"key11\": \"val11\", \"key12\": \"val12\"},\n            self.dbstore.jwk_load(\"name1\"),\n        )\n\n    def test_024_accout_search_inactive(self):\n        \"\"\"test DBstore.account_seach() method for alg field\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\"name\": \"name2\", \"status_id\": 7}\n        self.dbstore.account_update(data_dic, active=False)\n        self.assertIn(\n            (\"contact2\"), self.dbstore._account_search(\"alg\", \"alg2\", active=False)\n        )\n\n    def test_025_accout_search_inactive(self):\n        \"\"\"test DBstore.account_seach() method for alg field\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\"name\": \"name2\", \"status_id\": 7}\n        self.dbstore.account_update(data_dic, active=False)\n        self.assertFalse(self.dbstore._account_search(\"alg\", \"alg2\"))\n\n    def test_026_jkw_load(self):\n        \"\"\"test DBstore.jwk_load() for an not existing key\"\"\"\n        self.assertEqual({}, self.dbstore.jwk_load(\"not_existing\"))\n\n    def test_027_account_delete(self):\n        \"\"\"test DBstore.account_delete() for an existing key\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertTrue(self.dbstore.account_delete(\"name2\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    def test_028_accout_search_failure(self, id_check):\n        \"\"\"test DBstore.account_seach() method for alg field\"\"\"\n        id_check.return_value = True\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore._account_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Account search failed for column 'invalid_field' and pattern 'invalid_value': no such column: invalid_field\",\n            lcm.output,\n        )\n\n    def test_029_account_delete(self):\n        \"\"\"test DBstore.account_delete() for an non existing key\"\"\"\n        self.assertFalse(self.dbstore.account_delete(\"not_existing\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_030_account_lookup(self, mock_datestr):\n        \"\"\"test DBstore.account_lookup() for an existing value include eab_lid\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n            \"eab_kid\": \"eab_kid\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        mock_datestr.return_value = \"datestr\"\n        self.assertEqual(\n            {\n                \"id\": 1,\n                \"name\": \"name1\",\n                \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n                \"contact\": \"contact1\",\n                \"alg\": \"alg1\",\n                \"created_at\": \"datestr\",\n                \"eab_kid\": \"eab_kid\",\n                \"status_id\": 5,\n            },\n            self.dbstore.account_lookup(\"jwk\", '{\"key11\": \"val11\", \"key12\": \"val12\"}'),\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_031_account_lookup(self, mock_datestr):\n        \"\"\"test DBstore.account_lookup() for an existing value\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        mock_datestr.return_value = \"datestr\"\n        self.assertEqual(\n            {\n                \"id\": 1,\n                \"name\": \"name1\",\n                \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n                \"contact\": \"contact1\",\n                \"alg\": \"alg1\",\n                \"created_at\": \"datestr\",\n                \"eab_kid\": \"\",\n                \"status_id\": 5,\n            },\n            self.dbstore.account_lookup(\"jwk\", '{\"key11\": \"val11\", \"key12\": \"val12\"}'),\n        )\n\n    def test_032_account_lookup(self):\n        \"\"\"test DBstore.account_lookup() for an not existing value\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        self.assertFalse(self.dbstore.account_lookup(\"jwk\", \"jwk4\"))\n\n    def test_033_account_lookup(self):\n        \"\"\"test DBstore.account_lookup() for an non existing key\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore.account_lookup(\"non_existing_key\", \"name3\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Column: non_existing_key not found in account table\",\n            lcm.output,\n        )\n\n    def test_034_order_add(self):\n        \"\"\"test DBstore.order_add() method for a new entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.assertEqual(1, self.dbstore.order_add(data_dic))\n\n    def test_035_order_add(self):\n        \"\"\"test DBstore.order_add() method for a new entry with notbefore and notafter entries\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"name2\",\n            \"identifiers\": \"identifiers\",\n            \"notbefore\": 10,\n            \"notafter\": 20,\n            \"account\": \"name2\",\n            \"status\": 2,\n            \"expires\": \"25\",\n        }\n        self.assertEqual(2, self.dbstore.order_add(data_dic))\n\n    def test_036_order_add(self):\n        \"\"\"test DBstore.order_add() method - account lookup failed\"\"\"\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.assertFalse(self.dbstore.order_add(data_dic))\n\n    def test_037_order_lookup(self):\n        \"\"\"test DBstore.order_lookup() method for an existing entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name2\",\n            \"identifiers\": \"identifiers\",\n            \"notbefore\": 10,\n            \"notafter\": 20,\n            \"account\": \"name2\",\n            \"status\": 2,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        self.assertEqual(\n            {\n                \"status\": \"pending\",\n                \"notafter\": 20,\n                \"identifiers\": \"identifiers\",\n                \"expires\": 25,\n                \"notbefore\": 10,\n            },\n            self.dbstore.order_lookup(\"name\", \"name2\"),\n        )\n\n    def test_038_order_lookup(self):\n        \"\"\"test DBstore.order_lookup() method for a not existing entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name2\",\n            \"identifiers\": \"identifiers\",\n            \"notbefore\": 10,\n            \"notafter\": 20,\n            \"account\": \"name2\",\n            \"status\": 2,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        self.assertFalse(self.dbstore.order_lookup(\"name\", \"name3\"))\n\n    def test_039_order_lookup(self):\n        \"\"\"test DBstore.order_lookup() method for a not existing entry\"\"\"\n        self.assertFalse(self.dbstore.order_lookup(\"nam\", \"name1\"))\n\n    def test_040_order_lookup(self):\n        \"\"\"test DBstore.order_lookup() method with modified output list\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"name2\",\n            \"identifiers\": \"identifiers\",\n            \"notbefore\": 10,\n            \"notafter\": 20,\n            \"account\": \"name2\",\n            \"status\": 2,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        self.assertEqual(\n            {\"account__name\": \"name2\", \"name\": \"name2\", \"status\": \"pending\"},\n            self.dbstore.order_lookup(\n                \"name\", \"name2\", (\"name\", \"status__name\", \"account__name\")\n            ),\n        )\n\n    def test_041_authorization_add(self):\n        \"\"\"test DBstore.authorization_add() method\"\"\"\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.assertEqual(1, self.dbstore.authorization_add(data_dic))\n\n    def test_042_authorization_add(self):\n        \"\"\"test DBstore.authorization_add() method\"\"\"\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name2\", \"type\": \"type2\", \"value\": \"value2\", \"order\": 2}\n        self.assertEqual(2, self.dbstore.authorization_add(data_dic))\n\n    def test_043_authorization_update(self):\n        \"\"\"test DBstore.authorization_update() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token1\", \"expires\": \"25\"}\n        self.assertEqual(1, self.dbstore.authorization_update(data_dic))\n\n    def test_044_authorization_update(self):\n        \"\"\"test DBstore.authorization_update() method  no expires\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token2\"}\n        self.assertEqual(1, self.dbstore.authorization_update(data_dic))\n        self.assertEqual(\n            \"token2\",\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"token\"\n            ],\n        )\n\n    def test_045_authorization_update(self):\n        \"\"\"test DBstore.authorization_update() method  no expires\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"expires\": \"35\"}\n        self.assertEqual(1, self.dbstore.authorization_update(data_dic))\n        self.assertEqual(\n            35,\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"expires\"\n            ],\n        )\n\n    def test_046_authorization_update(self):\n        \"\"\"test DBstore.authorization_update() method  no expires\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"expires\": \"35\"}\n        self.assertFalse(self.dbstore.authorization_update(data_dic))\n\n    def test_047_authorization_update(self):\n        \"\"\"test DBstore.authorization_update() method  no expires\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"expires\": \"35\", \"status\": \"valid\"}\n        self.assertEqual(1, self.dbstore.authorization_update(data_dic))\n        self.assertEqual(\n            35,\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"expires\"\n            ],\n        )\n        self.assertEqual(\n            5,\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"status_id\"\n            ],\n        )\n        self.assertEqual(\n            \"valid\",\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"status__name\"\n            ],\n        )\n\n    def test_048_authorization_search(self):\n        \"\"\"test DBstore.authorization_search() by name\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token1\", \"expires\": \"25\"}\n        self.dbstore.authorization_update(data_dic)\n        self.assertIn(\n            \"token1\",\n            dict_from_row(self.dbstore._authorization_search(\"name\", \"name1\")[0])[\n                \"token\"\n            ],\n        )\n\n    def test_049_authorization_search(self):\n        \"\"\"test DBstore.authorization_search() by token\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token1\", \"expires\": \"25\"}\n        self.dbstore.authorization_update(data_dic)\n        self.assertIn(\n            \"name1\",\n            dict_from_row(self.dbstore._authorization_search(\"type\", \"type1\")[0])[\n                \"name\"\n            ],\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    def test_050_authorization_invalid(self, id_check):\n        \"\"\"test DBstore.authorization_search() by token\"\"\"\n        id_check.return_value = True\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token1\", \"expires\": \"25\"}\n        self.dbstore.authorization_update(data_dic)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore._authorization_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Authorization search failed for column 'invalid_field' and pattern 'invalid_value': no such column: invalid_field\",\n            lcm.output,\n        )\n\n    def test_051_authorization_lookup(self):\n        \"\"\"test DBstore.authorization_lookup() by name\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        self.assertEqual(\n            [{\"type\": \"type1\", \"value\": \"value1\"}],\n            self.dbstore.authorization_lookup(\"name\", \"name1\"),\n        )\n\n    def test_052_authorization_lookup(self):\n        \"\"\"test DBstore.authorization_lookup() by token\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"token\": \"token1\", \"expires\": \"25\"}\n        self.dbstore.authorization_update(data_dic)\n        self.assertEqual(\n            [{\"type\": \"type1\", \"value\": \"value1\"}],\n            self.dbstore.authorization_lookup(\"token\", \"token1\"),\n        )\n\n    def test_053_authorization_lookup(self):\n        \"\"\"test DBstore.authorization_lookup() for a not existing entry\"\"\"\n        self.assertFalse(self.dbstore.authorization_lookup(\"name\", \"name3\"))\n\n    def test_054_authorization_lookup(self):\n        \"\"\"test DBstore.authorization_lookup() for a not existing key\"\"\"\n        self.assertFalse(self.dbstore.authorization_lookup(\"nam\", \"name1\"))\n\n    def test_055_authorization_lookup(self):\n        \"\"\"test DBstore.authorization_lookup() for a modified output\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        self.assertEqual(\n            [\n                {\n                    \"name\": \"name1\",\n                    \"order__account__name\": \"name1\",\n                    \"order__name\": \"name1\",\n                }\n            ],\n            self.dbstore.authorization_lookup(\n                \"name\", \"name1\", (\"name\", \"order__name\", \"order__account__name\")\n            ),\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._authorization_search\")\n    def test_056_authorization_lookup_exc(self, mock_authz):\n        \"\"\"test DBstore.authorization_lookup() with exception\"\"\"\n        mock_authz.side_effect = Exception(\"mock_authz error\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore.authorization_lookup(\"nam\", \"name1\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Authorization lookup(column:nam, pattern:name1) failed with err: mock_authz error\",\n            lcm.output,\n        )\n\n    def test_057_challenge_add(self):\n        \"\"\"test DBstore.challenge_add() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.assertEqual(1, self.dbstore.challenge_add(\"value\", \"mtype\", data_dic))\n\n    def test_058_challenge_add(self):\n        \"\"\"test DBstore.challenge_add() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        data_dic = {\n            \"name\": \"challenge2\",\n            \"token\": \"token2\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type2\",\n        }\n        self.assertEqual(2, self.dbstore.challenge_add(\"value\", \"mtype\", data_dic))\n\n    def test_059_challenge_add(self):\n        \"\"\"test DBstore.challenge_add() method - authorization lookup failed\"\"\"\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.assertFalse(self.dbstore.challenge_add(\"value\", \"mtype\", data_dic))\n\n    def test_060_challenge_search(self):\n        \"\"\"test DBstore.challenge_search() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        self.assertIn((\"type1\"), self.dbstore._challenge_search(\"name\", \"challenge1\"))\n\n    def test_061_challenge_search(self):\n        \"\"\"test DBstore.challenge_search() method for not existing challenges\"\"\"\n        self.assertFalse(self.dbstore._challenge_search(\"name\", \"challenge3\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    def test_062_challenge_search_invalid(self, id_check):\n        \"\"\"test DBstore.challenge_search() method\"\"\"\n        id_check.return_value = True\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore._challenge_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Challenge search failed for column 'invalid_field' and pattern 'invalid_value': no such column: challenge.invalid_field\",\n            lcm.output,\n        )\n\n    def test_063_challenge_lookup(self):\n        \"\"\"test DBstore.challenge_lookup() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        self.assertEqual(\n            {\"status\": \"pending\", \"token\": \"token1\", \"type\": \"type1\"},\n            self.dbstore.challenge_lookup(\"name\", \"challenge1\"),\n        )\n\n    def test_064_challenge_lookup(self):\n        \"\"\"test DBstore.challenge_lookup() method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge2\",\n            \"token\": \"token2\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type2\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        self.assertEqual(\n            {\"status\": \"pending\", \"token\": \"token2\", \"type\": \"type2\"},\n            self.dbstore.challenge_lookup(\"name\", \"challenge2\"),\n        )\n\n    def test_065_challenge_lookup(self):\n        \"\"\"test DBstore.challenge_lookup() method  for a not existing entry\"\"\"\n        self.assertFalse(self.dbstore.challenge_lookup(\"name\", \"challenge3\"))\n\n    def test_066_challenge_lookup(self):\n        \"\"\"test DBstore.challenge_lookup() method for not existing key\"\"\"\n        self.assertFalse(self.dbstore.challenge_lookup(\"nam\", \"challenge1\"))\n\n    def test_067_challenge_lookup(self):\n        \"\"\"test DBstore.challenge_lookup() methodwith modified output\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        self.assertEqual(\n            {\n                \"authorization\": \"name1\",\n                \"authorization__order__account__name\": \"name1\",\n                \"name\": \"challenge1\",\n                \"authorization__order__name\": \"name\",\n            },\n            self.dbstore.challenge_lookup(\n                \"name\",\n                \"challenge1\",\n                (\n                    \"name\",\n                    \"authorization__name\",\n                    \"authorization__order__name\",\n                    \"authorization__order__account__name\",\n                ),\n            ),\n        )\n\n    def test_068_challenge_update(self):\n        \"\"\"test DBstore.challenge_update() method  without any parameter\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        data_dic = {\"name\": \"challenge1\"}\n        self.assertFalse(self.dbstore.challenge_update(data_dic))\n\n    def test_069_challenge_update(self):\n        \"\"\"test DBstore.challenge_update() method  with keyauth only\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        data_dic = {\"name\": \"challenge1\", \"status\": \"valid\", \"keyauthorization\": \"auth\"}\n        self.assertFalse(self.dbstore.challenge_update(data_dic))\n\n    def test_070_challenge_update(self):\n        \"\"\"test DBstore.challenge_update() method  with status only\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        data_dic = {\"name\": \"challenge1\", \"status\": \"valid\"}\n        self.assertFalse(self.dbstore.challenge_update(data_dic))\n\n    def test_071_challenge_update(self):\n        \"\"\"test DBstore.challenge_update() method  with both\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"status\": \"valid\",\n            \"keyauthorization\": \"auth1\",\n        }\n        self.assertFalse(self.dbstore.challenge_update(data_dic))\n\n    def test_072_order_search(self):\n        \"\"\"test DBstore.order_search() method (unsuccesful)\"\"\"\n        self.assertEqual(None, self.dbstore._order_search(\"name\", \"order\"))\n\n    def test_073_order_search(self):\n        \"\"\"test DBstore.order_search() method (succesful)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        self.assertEqual(\n            \"name\", dict_from_row(self.dbstore._order_search(\"name\", \"name\"))[\"name\"]\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    def test_074_order_search_invalid(self, id_check):\n        \"\"\"test DBstore.order_search() method (succesful)\"\"\"\n        id_check.return_value = True\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore._order_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Order search failed for column 'invalid_field' and pattern 'invalid_value': no such column: orders.invalid_field\",\n            lcm.output,\n        )\n\n    def test_075_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method (succesful)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name\",\n            \"header_info\": \"header_info1\",\n        }\n        self.assertEqual(1, self.dbstore.certificate_add(data_dic))\n\n    def test_076_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method (succesful)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname2\",\n            \"csr\": \"csr2\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info2\",\n        }\n        self.assertEqual(2, self.dbstore.certificate_add(data_dic))\n\n    def test_077_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method with error\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname2\",\n            \"csr\": \"csr2\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname3\",\n            \"csr\": \"csr3\",\n            \"order\": \"name2\",\n            \"error\": \"error3\",\n            \"header_info\": \"header_info1\",\n        }\n        self.assertEqual(3, self.dbstore.certificate_add(data_dic))\n\n    def test_078_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method for existing certificate\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n        }\n        self.assertEqual(1, self.dbstore.certificate_add(data_dic))\n\n    def test_079_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method existing certificate with error\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname2\",\n            \"csr\": \"csr2\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\"name\": \"certname2\", \"error\": \"error3\", \"poll_identifier\": None}\n        self.assertEqual(2, self.dbstore.certificate_add(data_dic))\n\n    def test_080_certificate_add(self):\n        \"\"\"test DBstore.certificate_add() method csr add\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"order\": \"name1\", \"header_info\": None}\n        self.assertEqual(1, self.dbstore.certificate_add(data_dic))\n        self.assertEqual(\n            {\n                \"cert\": None,\n                \"order\": \"name1\",\n                \"order__name\": \"name1\",\n                \"name\": \"certname1\",\n                \"csr\": \"\",\n            },\n            self.dbstore.certificate_lookup(\"name\", \"certname1\"),\n        )\n\n    def test_081_certificate_lookup(self):\n        \"\"\"test DBstore.certificate_lookup() by name (successful)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name1\",\n            \"header_info\": \"header_info1\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        self.assertEqual(\n            {\n                \"cert\": \"cert\",\n                \"order\": \"name1\",\n                \"order__name\": \"name1\",\n                \"name\": \"certname1\",\n                \"csr\": \"csr1\",\n            },\n            self.dbstore.certificate_lookup(\"name\", \"certname1\"),\n        )\n\n    def test_082_certificate_lookup(self):\n        \"\"\"test DBstore.certificate_lookup() by name (successful)\"\"\"\n        self.assertFalse(self.dbstore.certificate_lookup(\"name\", \"certname\"))\n\n    def test_083_certificate_lookup(self):\n        \"\"\"test DBstore.certificate_lookup() methodwith modified output\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name1\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n            \"header_info\": None,\n        }\n        self.dbstore.certificate_add(data_dic)\n        self.assertEqual(\n            {\"name\": \"certname1\", \"order__account__name\": \"name1\"},\n            self.dbstore.certificate_lookup(\n                \"name\", \"certname1\", (\"name\", \"order__account__name\")\n            ),\n        )\n\n    def test_084_certificate_lookup(self):\n        \"\"\"test DBstore.certificate_lookup() method with modified output\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name1\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        self.assertFalse(\n            self.dbstore.certificate_lookup(\n                \"name\", \"certname\", (\"name\", \"order__account__name\")\n            )\n        )\n\n    def test_085_certificate_search_invalid(self):\n        \"\"\"test DBstore.certificate_lookup() by name (successful)\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore._certificate_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in certificate table\",\n            lcm.output,\n        )\n\n    def test_086_certificate_account_check(self):\n        \"\"\"test DBstore.certificate_account_check() successful\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name1\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n        }\n        self.dbstore.certificate_add(data_dic)\n        self.assertEqual(\n            \"name1\", self.dbstore.certificate_account_check(\"name1\", \"cert_raw\")\n        )\n\n    def test_087_certificate_account_check(self):\n        \"\"\"test DBstore.certificate_account_check() cert lookup failed\"\"\"\n        self.assertFalse(self.dbstore.certificate_account_check(\"name1\", \"cert_failed\"))\n\n    def test_088_certificate_account_check(self):\n        \"\"\"test DBstore.certificate_account_check() cert lookup failed\"\"\"\n        self.assertFalse(self.dbstore.certificate_account_check(\"name1\", \"cert_failed\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.order_lookup\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.certificate_lookup\")\n    def test_089_certificate_account_check(self, mock_certlookup, mock_orderlookup):\n        \"\"\"test DBstore.certificate_account_check() order lookup failed\"\"\"\n        mock_certlookup.return_value = {\"order__name\": \"foo\"}\n        mock_orderlookup.return_value = {}\n        self.assertFalse(self.dbstore.certificate_account_check(\"name1\", \"cert_failed\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.order_lookup\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.certificate_lookup\")\n    def test_090_certificate_account_check(self, mock_certlookup, mock_orderlookup):\n        \"\"\"test DBstore.certificate_account_check() order lookup return different account_name\"\"\"\n        mock_certlookup.return_value = {\"order__name\": \"foo\"}\n        mock_orderlookup.return_value = {\"account__name\": \"xxx\"}\n        self.assertFalse(self.dbstore.certificate_account_check(\"name1\", \"cert_failed\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.order_lookup\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.certificate_lookup\")\n    def test_091_certificate_account_check(self, mock_certlookup, mock_orderlookup):\n        \"\"\"test DBstore.certificate_account_check() order lookup retured same account_name\"\"\"\n        mock_certlookup.return_value = {\"order__name\": \"foo\"}\n        mock_orderlookup.return_value = {\"account__name\": \"name1\"}\n        self.assertEqual(\n            \"foo\", self.dbstore.certificate_account_check(\"name1\", \"cert_failed\")\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.order_lookup\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.certificate_lookup\")\n    def test_092_certificate_account_check(self, mock_certlookup, mock_orderlookup):\n        \"\"\"test DBstore.certificate_account_check() order lookup retured same account_name\"\"\"\n        mock_certlookup.return_value = {\"order__name\": \"foo1\"}\n        mock_orderlookup.return_value = {\"account__name\": \"name1\"}\n        self.assertEqual(\n            \"foo1\", self.dbstore.certificate_account_check(None, \"cert_failed\")\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.order_lookup\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore.certificate_lookup\")\n    def test_093_certificate_account_check(self, mock_certlookup, mock_orderlookup):\n        \"\"\"test DBstore.certificate_account_check() order lookup retured no account__name\"\"\"\n        mock_certlookup.return_value = {\"order__name\": \"foo1\"}\n        mock_orderlookup.return_value = {\"foo\": \"name1\"}\n        self.assertFalse(self.dbstore.certificate_account_check(None, \"cert_failed\"))\n\n    def test_094_initialize(self):\n        \"\"\"test initialize function\"\"\"\n        self.assertEqual(None, self.initialize())\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_095_account_update(self, mock_datestr):\n        \"\"\"test account update all ok\"\"\"\n        mock_datestr.return_value = \"datestr\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n        update_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact20\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual(2, self.dbstore.account_update(update_dic))\n        result = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"alg\": \"alg2\",\n            \"contact\": \"contact20\",\n            \"created_at\": \"datestr\",\n            \"eab_kid\": \"\",\n            \"jwk\": \"jwk2\",\n            \"status_id\": 5,\n        }\n        self.assertEqual(result, self.dbstore.account_lookup(\"name\", \"name2\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_096_account_update(self, mock_datestr):\n        \"\"\"test account update without alg\"\"\"\n        mock_datestr.return_value = \"datestr\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact20\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n        update_dic = {\"jwk\": \"jwk2\", \"contact\": \"contact20\", \"name\": \"name2\"}\n        self.assertEqual(2, self.dbstore.account_update(update_dic))\n        result = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"alg\": \"alg2\",\n            \"contact\": \"contact20\",\n            \"created_at\": \"datestr\",\n            \"eab_kid\": \"\",\n            \"jwk\": \"jwk2\",\n            \"status_id\": 5,\n        }\n        self.assertEqual(result, self.dbstore.account_lookup(\"name\", \"name2\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_097_account_update(self, mock_datestr):\n        \"\"\"test account update without jwk\"\"\"\n        mock_datestr.return_value = \"datestr\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact21\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n        update_dic = {\"alg\": \"alg2\", \"contact\": \"contact20\", \"name\": \"name2\"}\n        self.assertEqual(2, self.dbstore.account_update(update_dic))\n        result = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"alg\": \"alg2\",\n            \"contact\": \"contact20\",\n            \"created_at\": \"datestr\",\n            \"eab_kid\": \"\",\n            \"jwk\": \"jwk2\",\n            \"status_id\": 5,\n        }\n        self.assertEqual(result, self.dbstore.account_lookup(\"name\", \"name2\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_098_account_update(self, mock_datestr):\n        \"\"\"test account update without jwk\"\"\"\n        mock_datestr.return_value = \"datestr\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n        update_dic = {\"alg\": \"alg2\", \"jwk\": \"jwk20\", \"name\": \"name2\"}\n        self.assertEqual(2, self.dbstore.account_update(update_dic))\n        result = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"alg\": \"alg2\",\n            \"contact\": \"contact2\",\n            \"created_at\": \"datestr\",\n            \"eab_kid\": \"\",\n            \"jwk\": \"jwk20\",\n            \"status_id\": 5,\n        }\n        self.assertEqual(result, self.dbstore.account_lookup(\"name\", \"name2\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.datestr_to_date\")\n    def test_099_account_update(self, mock_datestr):\n        \"\"\"test account update without eab_kid but eab_kid inserted in account_add()\"\"\"\n        mock_datestr.return_value = \"datestr\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"name\": \"name2\",\n            \"eab_kid\": \"eab_kid\",\n        }\n        self.assertEqual((\"name2\", True), self.dbstore.account_add(data_dic))\n        update_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact20\",\n            \"name\": \"name2\",\n        }\n        self.assertEqual(2, self.dbstore.account_update(update_dic))\n        result = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"alg\": \"alg2\",\n            \"contact\": \"contact20\",\n            \"created_at\": \"datestr\",\n            \"eab_kid\": \"eab_kid\",\n            \"jwk\": \"jwk2\",\n            \"status_id\": 5,\n        }\n        self.assertEqual(result, self.dbstore.account_lookup(\"name\", \"name2\"))\n\n    def test_100_account_update(self):\n        \"\"\"test account update - account.search() did not return anything\"\"\"\n        update_dic = {\n            \"alg\": \"alg2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact20\",\n            \"name\": \"name2\",\n        }\n        self.assertFalse(self.dbstore.account_update(update_dic))\n\n    def test_101_accountlist_get(self):\n        \"\"\"test DBstore.accountlist_get\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value1\", \"mtype1\", data_dic)\n        data_dic = {\n            \"name\": \"challenge2\",\n            \"token\": \"token2\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type2\",\n        }\n        self.dbstore.challenge_add(\"value2\", \"mtype2\", data_dic)\n        vlist = [\n            \"id\",\n            \"name\",\n            \"eab_kid\",\n            \"contact\",\n            \"created_at\",\n            \"jwk\",\n            \"alg\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__id\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__authorization__id\",\n            \"order__authorization__name\",\n            \"order__authorization__type\",\n            \"order__authorization__value\",\n            \"order__authorization__expires\",\n            \"order__authorization__token\",\n            \"order__authorization__created_at\",\n            \"order__authorization__status__id\",\n            \"order__authorization__status__name\",\n            \"order__authorization__challenge__id\",\n            \"order__authorization__challenge__name\",\n            \"order__authorization__challenge__token\",\n            \"order__authorization__challenge__expires\",\n            \"order__authorization__challenge__type\",\n            \"order__authorization__challenge__keyauthorization\",\n            \"order__authorization__challenge__created_at\",\n            \"order__authorization__challenge__status__id\",\n            \"order__authorization__challenge__status__name\",\n        ]\n        account_list = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"eab_kid\": \"\",\n            \"contact\": \"contact1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"alg\": \"alg1\",\n            \"order__id\": 1,\n            \"order__name\": \"name\",\n            \"order__status__id\": 1,\n            \"order__status__name\": \"invalid\",\n            \"order__notbefore\": \"\",\n            \"order__notafter\": \"\",\n            \"order__expires\": 25,\n            \"order__identifiers\": \"identifiers\",\n            \"order__authorization__id\": 1,\n            \"order__authorization__name\": \"name1\",\n            \"order__authorization__type\": \"type1\",\n            \"order__authorization__value\": \"value1\",\n            \"order__authorization__expires\": None,\n            \"order__authorization__token\": None,\n            \"order__authorization__status__id\": 2,\n            \"order__authorization__status__name\": \"pending\",\n            \"order__authorization__challenge__id\": 1,\n            \"order__authorization__challenge__name\": \"challenge1\",\n            \"order__authorization__challenge__token\": \"token1\",\n            \"order__authorization__challenge__expires\": 25,\n            \"order__authorization__challenge__type\": \"type1\",\n            \"order__authorization__challenge__keyauthorization\": None,\n            \"order__authorization__challenge__status__id\": 2,\n            \"order__authorization__challenge__status__name\": \"pending\",\n        }\n        (result_vlist, result_account_list) = self.dbstore.accountlist_get()\n        self.assertEqual(vlist, result_vlist)\n        self.assertTrue(\n            set(account_list.items()).issubset(set(result_account_list[0].items()))\n        )\n\n    def test_102_authorizations_expired_search_invalid(self):\n        \"\"\"test DBstore.authorizations_expired_search()\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore.authorizations_expired_search(\n                    \"invalid_field\", \"invalid_value\"\n                )\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in authorization table\",\n            lcm.output,\n        )\n\n    def test_103_authorizations_expired_search(self):\n        \"\"\"test DBstore.authorizations_expired_search()\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        result = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"expires\": None,\n            \"value\": \"value1\",\n            \"token\": None,\n            \"status__id\": 2,\n            \"status__name\": \"pending\",\n            \"order__id\": 1,\n            \"order__name\": \"name\",\n        }\n        result_list = self.dbstore.authorizations_expired_search(\"name\", \"name1\")\n        self.assertTrue(set(result.items()).issubset(set(result_list[0].items())))\n\n    def test_104_certificate_delete(self):\n        \"\"\"test DBstore.certificate_delete() method (succesful)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        result = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name\",\n            \"order__name\": \"name\",\n            \"cert\": None,\n        }\n        self.assertEqual(result, self.dbstore.certificate_lookup(\"name\", \"certname1\"))\n        self.dbstore.certificate_delete(\"name\", \"certname1\")\n        # check if certificate is deleted\n        self.assertFalse(self.dbstore.certificate_lookup(\"name\", \"certname1\"))\n\n    def test_105_certificate_delete(self):\n        \"\"\"test DBstore.certificate_delete() method (unsuccesful bcs of invalid column)\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        result = {\n            \"name\": \"certname1\",\n            \"csr\": \"csr1\",\n            \"order\": \"name\",\n            \"order__name\": \"name\",\n            \"cert\": None,\n        }\n        self.assertEqual(result, self.dbstore.certificate_lookup(\"name\", \"certname1\"))\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore.certificate_delete(\"nam_e\", \"certname1\")\n        self.assertIn(\n            \"WARNING:test_a2c:Column: nam_e not found in certificate table\",\n            lcm.output,\n        )\n        # check if certificate is NOT deleted\n        self.assertTrue(self.dbstore.certificate_lookup(\"name\", \"certname1\"))\n\n    def test_105_certificatelist_get(self):\n        \"\"\"test DBstore.certificatelist_get()\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\n            \"name\": \"certname1\",\n            \"cert\": \"cert\",\n            \"cert_raw\": \"cert_raw\",\n            \"poll_identifier\": \"poll_identifier\",\n        }\n        self.assertEqual(1, self.dbstore.certificate_add(data_dic))\n        vlist = [\n            \"id\",\n            \"name\",\n            \"cert_raw\",\n            \"csr\",\n            \"poll_identifier\",\n            \"created_at\",\n            \"issue_uts\",\n            \"expire_uts\",\n            \"order__id\",\n            \"order__name\",\n            \"order__status__name\",\n            \"order__notbefore\",\n            \"order__notafter\",\n            \"order__expires\",\n            \"order__identifiers\",\n            \"order__account__name\",\n            \"order__account__contact\",\n            \"order__account__created_at\",\n            \"order__account__jwk\",\n            \"order__account__alg\",\n            \"order__account__eab_kid\",\n        ]\n        certlist = {\n            \"id\": 1,\n            \"name\": \"certname1\",\n            \"cert_raw\": \"cert_raw\",\n            \"csr\": \"csr1\",\n            \"poll_identifier\": \"poll_identifier\",\n            \"issue_uts\": 0,\n            \"expire_uts\": 0,\n            \"order__id\": 1,\n            \"order__name\": \"name\",\n            \"order__status__name\": 1,\n            \"order__notbefore\": \"\",\n            \"order__notafter\": \"\",\n            \"order__expires\": 25,\n            \"order__identifiers\": \"identifiers\",\n            \"order__account__name\": \"name1\",\n            \"order__account__contact\": \"contact1\",\n            \"order__account__jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"order__account__alg\": \"alg1\",\n            \"order__account__eab_kid\": \"\",\n        }\n        (result_vlist, result_certifcate_list) = self.dbstore.certificatelist_get()\n        self.assertEqual(vlist, result_vlist)\n        self.assertTrue(\n            set(certlist.items()).issubset(set(result_certifcate_list[0].items()))\n        )\n\n    def test_106_dbversion(self):\n        \"\"\"test db_version\"\"\"\n        self.assertEqual(\n            (self.dbversion, \"tools/db_update.py\"), self.dbstore.dbversion_get()\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_107_dbversion(self, mock_open, mock_close):\n        \"\"\"test db_version no result\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchone = Mock(return_value=[])\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((None, \"tools/db_update.py\"), self.dbstore.dbversion_get())\n        self.assertIn(\n            \"ERROR:test_a2c:DBStore.dbversion_get() lookup failed\", lcm.output\n        )\n\n    def test_108_certificates_search_failed(self):\n        \"\"\"test DBstore.certificates_search()\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore.certificates_search(\n                    \"invalid_field\",\n                    \"invalid_value\",\n                )\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in certificate table\",\n            lcm.output,\n        )\n\n    def test_109_certificates_search(self):\n        \"\"\"test DBstore.certificates_search()\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"cert\": \"cert\", \"cert_raw\": \"cert_raw\"}\n        self.dbstore.certificate_add(data_dic)\n        self.assertEqual(\n            [\n                {\n                    \"name\": \"certname1\",\n                    \"csr\": \"csr1\",\n                    \"cert\": \"cert\",\n                    \"order__name\": \"name\",\n                    \"order\": \"name\",\n                }\n            ],\n            self.dbstore.certificates_search(\"cert\", \"cert\"),\n        )\n        self.assertEqual(\n            [\n                {\n                    \"name\": \"certname1\",\n                    \"csr\": \"csr1\",\n                    \"cert\": \"cert\",\n                    \"order__name\": \"name\",\n                    \"order\": \"name\",\n                }\n            ],\n            self.dbstore.certificates_search(\"cert_raw\", \"cert_raw\"),\n        )\n        self.assertEqual(\n            [\n                {\n                    \"name\": \"certname1\",\n                    \"csr\": \"csr1\",\n                    \"cert\": \"cert\",\n                    \"order__name\": \"name\",\n                    \"order\": \"name\",\n                }\n            ],\n            self.dbstore.certificates_search(\"certificate.name\", \"certname1\"),\n        )\n        self.assertEqual(\n            [{\"cert\": \"cert\", \"name\": \"certname1\"}],\n            self.dbstore.certificates_search(\n                \"certificate.name\", \"certname1\", vlist=[\"name\", \"cert\"]\n            ),\n        )\n\n    def test_110_certificates_search(self):\n        \"\"\"test DBstore.certificates_search() no result\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"cert\": \"cert\", \"cert_raw\": \"cert_raw\"}\n        self.dbstore.certificate_add(data_dic)\n        self.assertFalse(self.dbstore.certificates_search(\"cert\", \"cert1\"))\n\n    def test_111_certificates_search(self):\n        \"\"\"test DBstore.certificates_search() rewrite order_status_id\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"csr\": \"csr1\", \"order\": \"name\"}\n        self.dbstore.certificate_add(data_dic)\n        data_dic = {\"name\": \"certname1\", \"cert\": \"cert\", \"cert_raw\": \"cert_raw\"}\n        self.dbstore.certificate_add(data_dic)\n        self.assertEqual(\n            [\n                {\n                    \"name\": \"certname1\",\n                    \"csr\": \"csr1\",\n                    \"cert\": \"cert\",\n                    \"order__name\": \"name\",\n                    \"order\": \"name\",\n                }\n            ],\n            self.dbstore.certificates_search(\"order__status_id\", 1),\n        )\n\n    def test_112_challenge_search(self):\n        \"\"\"test DBstore.challenge_search method\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        result = [\n            {\n                \"name\": \"challenge1\",\n                \"status\": \"pending\",\n                \"status__name\": \"pending\",\n                \"token\": \"token1\",\n                \"type\": \"type1\",\n            }\n        ]\n        self.assertEqual(\n            result, self.dbstore.challenges_search(\"challenge.name\", \"challenge1\")\n        )\n        self.assertEqual(\n            result, self.dbstore.challenges_search(\"challenge.token\", \"token1\")\n        )\n        self.assertEqual(\n            result, self.dbstore.challenges_search(\"status__name\", \"pending\")\n        )\n        self.assertEqual(\n            [{\"name\": \"challenge1\", \"token\": \"token1\"}],\n            self.dbstore.challenges_search(\n                \"status__name\", \"pending\", vlist=[\"name\", \"token\"]\n            ),\n        )\n\n    def test_113_challenge_search(self):\n        \"\"\"test DBstore.challenge_search failed\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.dbstore.order_add(data_dic)\n        data_dic = {\"name\": \"name1\", \"type\": \"type1\", \"value\": \"value1\", \"order\": 1}\n        self.dbstore.authorization_add(data_dic)\n        data_dic = {\n            \"name\": \"challenge1\",\n            \"token\": \"token1\",\n            \"authorization\": \"name1\",\n            \"expires\": 25,\n            \"type\": \"type1\",\n        }\n        self.dbstore.challenge_add(\"value\", \"mtype\", data_dic)\n        self.assertFalse(self.dbstore.challenges_search(\"challenge.name\", \"challenge\"))\n\n    def test_114_challenges_search_invalid(self):\n        \"\"\"test DBstore.challenges_search() invalid field\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore.challenges_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in challenge table\",\n            lcm.output,\n        )\n\n    def test_115_db_update_orders(self):\n        \"\"\"test dbupdate - alter orders table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(\n            return_value=[[2, \"identifiers\", \"varchar\"]]\n        )\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore._db_update_orders()\n        self.assertIn(\n            \"INFO:test_a2c:alter orders table - change identifier field type to TEXT\",\n            lcm.output,\n        )\n\n    def test_116_db_update_orders(self):\n        \"\"\"test dbupdate - alter orders table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"foo\"]])\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore._db_update_orders()\n        self.assertIn(\n            \"INFO:test_a2c:alter challenge orders - add profile\",\n            lcm.output,\n        )\n\n    def test_117_db_update_authorization(self):\n        \"\"\"test dbupdate - alter authorizations table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"value\", \"varchar\"]])\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore._db_update_authorization()\n        self.assertIn(\n            \"INFO:test_a2c:alter authorization table - change value field type to TEXT\",\n            lcm.output,\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_118_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate - not alter certificates table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(\n            return_value=[[2, \"poll_identifier\"], [2, \"issue_uts\"], [2, \"expire_uts\"]]\n        )\n        self.dbstore.cursor.fetchone = Mock(return_value=[1, 2, 3, 4, 5])\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertTrue = self.dbstore.db_update()\n        self.assertIn(\"INFO:test_a2c:alter challenge table - add validated\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_119_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate - not alter challenge table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"validated\"]])\n        self.dbstore.cursor.fetchone = Mock(return_value=[1, 2, 3, 4, 5])\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertTrue = self.dbstore.db_update()\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add poll_identifier\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add issue_uts\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add expire_uts\", lcm.output\n        )\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_120_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate - not alter account table\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"eab_kid\"]])\n        self.dbstore.cursor.fetchone = Mock(return_value=[1, 2, 3, 4, 5])\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertTrue = self.dbstore.db_update()\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add poll_identifier\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add issue_uts\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add expire_uts\", lcm.output\n        )\n        self.assertIn(\"INFO:test_a2c:alter challenge table - add validated\", lcm.output)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_121_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate - status update\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"foo\"]])\n        self.dbstore.cursor.fetchone = Mock(\n            side_effect=[None, [1, 2, 3, 4, 5], [1, 2], [0, 2]]\n        )\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore.db_update()\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add poll_identifier\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add issue_uts\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add expire_uts\", lcm.output\n        )\n        self.assertIn(\"INFO:test_a2c:alter challenge table - add validated\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:adding additional status\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:create cliaccount table\", lcm.output)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_122_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate - housekeeping update\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"foo\"]])\n        self.dbstore.cursor.fetchone = Mock(\n            side_effect=[None, [2, 2, 3, 4, 5], [1, 2], [1, 2]]\n        )\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore.db_update()\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add poll_identifier\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add issue_uts\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add expire_uts\", lcm.output\n        )\n        self.assertIn(\"INFO:test_a2c:alter challenge table - add validated\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:adding additional status\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:create housekeeping table and trigger\", lcm.output)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_123_db_update(self, mock_open, mock_close):\n        \"\"\"test dbupdate -  update\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchall = Mock(return_value=[[2, \"foo\"]])\n        self.dbstore.cursor.fetchone = Mock(\n            side_effect=[None, [1, 2, 3, 4, 5], [2, 2], [2, 2]]\n        )\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore.db_update()\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add poll_identifier\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add issue_uts\", lcm.output\n        )\n        self.assertIn(\n            \"INFO:test_a2c:alter certificate table - add expire_uts\", lcm.output\n        )\n        self.assertIn(\"INFO:test_a2c:alter challenge table - add validated\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:adding additional status\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:alter account table - add eab_kid\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:create cahandler table\", lcm.output)\n        self.assertIn(\"INFO:test_a2c:create cliaccount table\", lcm.output)\n\n    def test_124_order_update(self):\n        \"\"\"test DBstore.order_add() method for a new entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name\",\n            \"identifiers\": \"identifiers\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.assertEqual(1, self.dbstore.order_add(data_dic))\n        update_dic = {\"name\": \"name\", \"status\": \"valid\"}\n        self.dbstore.order_update(update_dic)\n        result = {\n            \"expires\": 25,\n            \"notbefore\": 0,\n            \"notafter\": 0,\n            \"identifiers\": \"identifiers\",\n            \"status\": \"valid\",\n        }\n        self.assertEqual(result, self.dbstore.order_lookup(\"name\", \"name\"))\n\n    def test_125_order_update(self):\n        \"\"\"test DBstore.order_add() method for a new entry\"\"\"\n        data_dic = {\n            \"alg\": \"alg1\",\n            \"jwk\": '{\"key11\": \"val11\", \"key12\": \"val12\"}',\n            \"contact\": \"contact1\",\n            \"name\": \"name1\",\n        }\n        self.dbstore.account_add(data_dic)\n        data_dic = {\n            \"name\": \"name1\",\n            \"identifiers\": \"identifiers2\",\n            \"account\": \"name1\",\n            \"status\": 1,\n            \"expires\": \"25\",\n        }\n        self.assertEqual(1, self.dbstore.order_add(data_dic))\n        data_dic = {\n            \"name\": \"name2\",\n            \"identifiers\": \"identifiers2\",\n            \"account\": \"name1\",\n            \"status\": 2,\n            \"expires\": \"25\",\n        }\n        self.assertEqual(2, self.dbstore.order_add(data_dic))\n        expected_result = {\n            \"account__contact\": \"contact1\",\n            \"account__id\": 1,\n            \"account__name\": \"name1\",\n            \"expires\": 25,\n            \"id\": 2,\n            \"identifiers\": \"identifiers2\",\n            \"name\": \"name2\",\n            \"status__id\": 2,\n            \"status__name\": \"pending\",\n        }\n        order_list = self.dbstore.orders_invalid_search(\"name\", \"name2\")\n        self.assertTrue(\n            set(expected_result.items()).issubset(set(order_list[0].items()))\n        )\n\n    def test_126_orders_invalid_search_invalid(self):\n        \"\"\"test DBstore.orders_invalid_search()\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.dbstore.orders_invalid_search(\"invalid_field\", \"invalid_value\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in orders table\",\n            lcm.output,\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_create\")\n    @patch(\"examples.db_handler.wsgi_handler.load_config\")\n    def test_127__init__(self, mock_cfg, mock_create):\n        \"\"\"test init no dbfile specifiction\"\"\"\n        self.dbstore.db_name = None\n        mock_create.return_value = True\n        self.dbstore.__init__()\n        self.assertTrue(mock_cfg.called)\n        self.assertIn(\"acme_srv.db\", self.dbstore.db_name)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_create\")\n    @patch(\"examples.db_handler.wsgi_handler.load_config\")\n    def test_128__init__(self, mock_cfg, mock_create):\n        \"\"\"test init no dbfile specifiction\"\"\"\n        self.dbstore.db_name = None\n        mock_create.return_value = True\n        mock_cfg.return_value = {\"FOO\": {\"foo\": \"bar\"}}\n        self.dbstore.__init__()\n        self.assertTrue(mock_cfg.called)\n        self.assertIn(\"acme_srv.db\", self.dbstore.db_name)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_create\")\n    @patch(\"examples.db_handler.wsgi_handler.load_config\")\n    def test_129__init__(self, mock_cfg, mock_create):\n        \"\"\"test init DBhandler but no dbfile specifiction\"\"\"\n        self.dbstore.db_name = None\n        mock_create.return_value = True\n        mock_cfg.return_value = {\"DBhandler\": {\"foo\": \"bar\"}}\n        self.dbstore.__init__()\n        self.assertTrue(mock_cfg.called)\n        self.assertIn(\"acme_srv.db\", self.dbstore.db_name)\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_create\")\n    @patch(\"examples.db_handler.wsgi_handler.load_config\")\n    def test_130__init__(self, mock_cfg, mock_create):\n        \"\"\"test init DBhandler but no dbfile specifiction\"\"\"\n        self.dbstore.db_name = None\n        mock_create.return_value = True\n        mock_cfg.return_value = {\"DBhandler\": {\"dbfile\": \"foo.db\"}}\n        self.dbstore.__init__()\n        self.assertTrue(mock_cfg.called)\n        self.assertIn(\"foo.db\", self.dbstore.db_name)\n\n    def test_131_cahandler_add(self):\n        \"\"\"test DBstore.cahandler_add() method for a new entry\"\"\"\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n        data_dic = {\"name\": \"name2\", \"value1\": \"value1\", \"value2\": \"value2\"}\n        self.assertEqual(2, self.dbstore.cahandler_add(data_dic))\n\n    def test_132_cahandler_add(self):\n        \"\"\"test DBstore.cahandler_add() method for an existing entry\"\"\"\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\", \"value2\": \"value2\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n\n    def test_133_cahandler_lookup(self):\n        \"\"\"test DBstore.cahandler_lookup() method\"\"\"\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n        result = {\"name\": \"name1\", \"value1\": \"value1\", \"value2\": \"\"}\n        self.assertEqual(\n            result,\n            self.dbstore.cahandler_lookup(\n                \"name\", \"name1\", (\"name\", \"value1\", \"value2\")\n            ),\n        )\n\n    def test_134_cahandler_search(self):\n        \"\"\"test DBstore.cahandler_lookup() method\"\"\"\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n        result = {\"name\": \"name1\", \"value1\": \"value1\", \"value2\": \"\"}\n        self.assertIn((\"name1\"), self.dbstore._cahandler_search(\"name\", \"name1\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_135_cahandler_search(self, mock_open, mock_close, idchk):\n        \"\"\"test DBstore.cahandler_lookup() triggers exception\"\"\"\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchone = Exception(\"foo\")\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        idchk.return_value = True\n        result = {\"name\": \"name1\", \"value1\": \"value1\", \"value2\": \"\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore._cahandler_search(\"name\", \"name1\"))\n        self.assertIn(\n            \"ERROR:test_a2c:CA handler search failed for column 'name' and pattern 'name1': 'Exception' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    def test_136_cahandler_search_invalid(self, id_check):\n        \"\"\"test DBstore.cahandler_lookup() method\"\"\"\n        id_check.return_value = False\n        data_dic = {\"name\": \"name1\", \"value1\": \"value1\"}\n        self.assertEqual(1, self.dbstore.cahandler_add(data_dic))\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore._cahandler_search(\"name\", \"name1\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Column: name not found in cahandler table\", lcm.output\n        )\n\n    def test_137_hkparameter_add(self):\n        \"\"\"test DBstore.hkparameter_add() method for a new entry\"\"\"\n        data_dic = {\"name\": \"name1\", \"value\": \"value1\"}\n        self.assertEqual((\"name1\", True), self.dbstore.hkparameter_add(data_dic))\n        data_dic = {\"name\": \"name2\", \"value\": \"value2\"}\n        self.assertEqual((\"name2\", True), self.dbstore.hkparameter_add(data_dic))\n\n    def test_138_hkparameter_add(self):\n        \"\"\"test DBstore.hkparameter_add() method for an existing entry\"\"\"\n        data_dic = {\"name\": \"name1\", \"value\": \"value1\"}\n        self.assertEqual((\"name1\", True), self.dbstore.hkparameter_add(data_dic))\n        data_dic = {\"name\": \"name1\", \"value\": \"value2\"}\n        self.assertEqual((\"name1\", False), self.dbstore.hkparameter_add(data_dic))\n\n    def test_139_hkparameter_get(self):\n        \"\"\"test DBstore.hkparameter_add() method for a new entry\"\"\"\n        data_dic = {\"name\": \"name1\", \"value\": \"value1\"}\n        self.assertEqual((\"name1\", True), self.dbstore.hkparameter_add(data_dic))\n        self.assertEqual(\"value1\", self.dbstore.hkparameter_get(\"name1\"))\n\n    def test_140_cliaccount_add(self):\n        \"\"\"test DBstore.cliaccount_add() method for an new entry\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        data_dic = {\n            \"name\": \"name2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"cliadmin\": False,\n            \"certificateadmin\": False,\n            \"reportadmin\": False,\n        }\n        self.assertEqual(2, self.dbstore.cliaccount_add(data_dic))\n        cli_account_list = self.dbstore.cliaccountlist_get()\n        result1 = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": 1,\n            \"reportadmin\": 1,\n            \"certificateadmin\": 1,\n        }\n        result2 = {\n            \"id\": 2,\n            \"name\": \"name2\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact2\",\n            \"cliadmin\": 0,\n            \"reportadmin\": 0,\n            \"certificateadmin\": 0,\n        }\n        self.assertTrue(set(result1.items()).issubset(set(cli_account_list[0].items())))\n        self.assertTrue(set(result2.items()).issubset(set(cli_account_list[1].items())))\n\n    def test_141_cliaccount_add(self):\n        \"\"\"test DBstore.cliaccount_add() update jwk\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk2\",\n            \"cliadmin\": False,\n            \"certificateadmin\": False,\n            \"reportadmin\": False,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        result = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"jwk\": \"jwk2\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": 0,\n            \"reportadmin\": 0,\n            \"certificateadmin\": 0,\n        }\n        cli_account_list = self.dbstore.cliaccountlist_get()\n        self.assertTrue(set(result.items()).issubset(set(cli_account_list[0].items())))\n\n    def test_142_cliaccount_add(self):\n        \"\"\"test DBstore.cliaccount_add() update contact\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        data_dic = {\n            \"name\": \"name1\",\n            \"contact\": \"contact2\",\n            \"cliadmin\": False,\n            \"certificateadmin\": False,\n            \"reportadmin\": False,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        result = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact2\",\n            \"cliadmin\": 0,\n            \"reportadmin\": 0,\n            \"certificateadmin\": 0,\n        }\n        cli_account_list = self.dbstore.cliaccountlist_get()\n        self.assertTrue(set(result.items()).issubset(set(cli_account_list[0].items())))\n\n    def test_143_cliaccount_delete(self):\n        \"\"\"test DBstore.cliaccount_delete() sucessful\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        self.dbstore.cliaccount_delete({\"name\": \"name1\"})\n        self.assertFalse(self.dbstore.cliaccountlist_get())\n\n    def test_144_cliaccount_delete(self):\n        \"\"\"test DBstore.cliaccount_delete() sucessful\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.dbstore.cliaccount_delete({\"name\": \"name2\"})\n        self.assertIn(\n            \"ERROR:test_a2c:CLI account delete failed: no entry found for kid 'name2'\",\n            lcm.output,\n        )\n        cli_account_list = self.dbstore.cliaccountlist_get()\n        result = {\n            \"id\": 1,\n            \"name\": \"name1\",\n            \"jwk\": \"jwk1\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": 1,\n            \"reportadmin\": 1,\n            \"certificateadmin\": 1,\n        }\n        self.assertTrue(set(result.items()).issubset(set(cli_account_list[0].items())))\n\n    def test_145_cli_jwk_load(self):\n        \"\"\"test cli_jwk_load for an existing entry\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        self.assertEqual({\"foo\": \"bar\"}, self.dbstore.cli_jwk_load(\"name1\"))\n\n    def test_146_cli_jwk_load(self):\n        \"\"\"test cli_jwk_load for a not existing entry\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        self.assertFalse(self.dbstore.cli_jwk_load(\"name2\"))\n\n    def test_147_cli_permissions_get(self):\n        \"\"\"test cli_jwk_load for an existing entry\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        self.assertEqual(\n            {\"cliadmin\": 1, \"reportadmin\": 1, \"certificateadmin\": 1},\n            self.dbstore.cli_permissions_get(\"name1\"),\n        )\n\n    def test_148_cli_permissions_get(self):\n        \"\"\"test cli_jwk_load for a not existing entry\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": '{\"foo\": \"bar\"}',\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        self.assertFalse(self.dbstore.cli_permissions_get(\"name2\"))\n\n    def test_149__cliaccount_search(self):\n        \"\"\"test cliaccount_search exception\"\"\"\n        data_dic = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        self.assertEqual(1, self.dbstore.cliaccount_add(data_dic))\n        result = {\n            \"name\": \"name1\",\n            \"jwk\": \"jwk\",\n            \"contact\": \"contact1\",\n            \"cliadmin\": True,\n            \"certificateadmin\": True,\n            \"reportadmin\": True,\n        }\n        cli_account_list = self.dbstore._cliaccount_search(\"name\", \"name1\")\n        self.assertTrue(\n            set(result.items()).issubset(set(dict(cli_account_list).items()))\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_150__cliaccount_search(self, mock_open, mock_close, idchk):\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchone = Exception(\"foo\")\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        idchk.return_value = True\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore._cliaccount_search(\"name\", \"name2\"))\n        self.assertIn(\n            \"ERROR:test_a2c:CLI account search failed for column 'name' and pattern 'name2': 'Exception' object is not callable\",\n            lcm.output,\n        )\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._identifier_check\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_close\")\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._db_open\")\n    def test_151__cliaccount_search(self, mock_open, mock_close, idchk):\n        self.dbstore.cursor = Mock()\n        self.dbstore.cursor.fetchone = Exception(\"foo\")\n        mock_open.return_value = Mock()\n        mock_close.return_value = Mock()\n        idchk.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore._cliaccount_search(\"name\", \"name2\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Column: name not found in cliaccount table\", lcm.output\n        )\n\n    def test_152_status_search_invalid(self):\n        \"\"\"test DBstore.status_search() method (unsuccesful)\"\"\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (None, None),\n                self.dbstore._status_search(\"invalid_field\", \"invalid_status\"),\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Column: invalid_field not found in status table\",\n            lcm.output,\n        )\n\n    def test_153_table_check(self):\n        \"\"\"test DBstore.table_check() method\"\"\"\n        self.assertTrue(self.dbstore._table_check(\"account\"))\n        self.assertFalse(self.dbstore._table_check(\"accounts\"))\n\n    def test_154_identifier_check(self):\n        \"\"\"test DBstore._identifier_check() method\"\"\"\n        self.assertTrue(self.dbstore._identifier_check(\"account\", \"contact\"))\n        self.assertFalse(self.dbstore._identifier_check(\"account\", \"unkown\"))\n        self.assertTrue(self.dbstore._identifier_check(\"order\", \"name\"))\n        self.assertFalse(self.dbstore._identifier_check(\"order\", \"name1\"))\n        self.assertTrue(self.dbstore._identifier_check(\"account\", \"order.profile\"))\n        self.assertFalse(self.dbstore._identifier_check(\"account\", \"order.profile1\"))\n        self.assertTrue(self.dbstore._identifier_check(\"account\", \"order__profile\"))\n        self.assertFalse(self.dbstore._identifier_check(\"account\", \"order__profile1\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._table_check\")\n    def test_155_identifier_check(self, mock_tg):\n        \"\"\"test DBstore._identifier_check() method\"\"\"\n        mock_tg.return_value = True\n        self.assertTrue(self.dbstore._identifier_check(\"account\", \"contact\"))\n\n    @patch(\"examples.db_handler.wsgi_handler.DBstore._table_check\")\n    def test_156_identifier_check(self, mock_tg):\n        \"\"\"test DBstore._identifier_check() method\"\"\"\n        mock_tg.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.dbstore._identifier_check(\"account\", \"contact\"))\n        self.assertIn(\n            \"WARNING:test_a2c:Table 'account' does not exist in the database.\",\n            lcm.output,\n        )\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "test/test_xca_ca_handler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"unittests for openssl_ca_handler\"\"\"\n# pylint: disable=C0302, C0415, R0904, R0913, W0212\nimport sys\nimport os\nimport unittest\nfrom unittest.mock import patch, Mock, MagicMock\n\n# from OpenSSL import crypto\nimport shutil\nfrom cryptography import x509\nfrom cryptography.x509 import (\n    BasicConstraints,\n    ExtendedKeyUsage,\n    SubjectKeyIdentifier,\n    AuthorityKeyIdentifier,\n    KeyUsage,\n    SubjectAlternativeName,\n)\nimport configparser\n\nsys.path.insert(0, \".\")\nsys.path.insert(1, \"..\")\n\n\ndef _prepare(dir_path):\n    \"\"\"prepare testing\"\"\"\n    # copy clean database\n    if os.path.exists(dir_path + \"/ca/acme2certifier-clean.xdb\"):\n        shutil.copy(\n            dir_path + \"/ca/acme2certifier-clean.xdb\",\n            dir_path + \"/ca/acme2certifier.xdb\",\n        )\n\n\ndef _cleanup(dir_path):\n    \"\"\"cleanup function\"\"\"\n    # remove old db\n    if os.path.exists(dir_path + \"/ca/acme2certifier.xdb\"):\n        os.remove(dir_path + \"/ca/acme2certifier.xdb\")\n\n\ndef return_input(*args, **kwargs):\n    \"\"\"this function just returns input to output\"\"\"\n    _foo = kwargs\n    return args\n\n\nclass TestACMEHandler(unittest.TestCase):\n    \"\"\"test class for cgi_handler\"\"\"\n\n    def setUp(self):\n        \"\"\"setup unittest\"\"\"\n        import logging\n        from examples.ca_handler.xca_ca_handler import CAhandler\n\n        logging.basicConfig(level=logging.CRITICAL)\n        self.logger = logging.getLogger(\"test_a2c\")\n        self.cahandler = CAhandler(False, self.logger)\n        self.dir_path = os.path.dirname(os.path.realpath(__file__))\n        _prepare(self.dir_path)\n\n    def tearDown(self):\n        \"\"\"teardown\"\"\"\n        _cleanup(self.dir_path)\n\n    def test_001_default(self):\n        \"\"\"default test which always passes\"\"\"\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_002_check_config(self):\n        \"\"\"CAhandler._config_check with an empty config_dict\"\"\"\n        self.assertEqual(\n            \"xdb_file must be specified in config file\", self.cahandler._config_check()\n        )\n\n    def test_003_check_config(self):\n        \"\"\"CAhandler._config_check non existing xdb\"\"\"\n        self.cahandler.xdb_file = \"foo\"\n        self.assertEqual(\"xdb_file foo does not exist\", self.cahandler._config_check())\n\n    @patch(\"os.path.exists\")\n    def test_004_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check xdb exists but no issuing ca_name\"\"\"\n        self.cahandler.xdb_file = \"foo\"\n        mock_file.return_value = True\n        self.assertEqual(\n            \"issuing_ca_name must be set in config file\", self.cahandler._config_check()\n        )\n\n    @patch(\"os.path.exists\")\n    def test_005_check_config(self, mock_file):\n        \"\"\"CAhandler._config_check xdb exists but no issuing ca_name\"\"\"\n        self.cahandler.xdb_file = \"foo\"\n        self.cahandler.issuing_ca_name = \"foo\"\n        mock_file.return_value = True\n        self.assertFalse(self.cahandler._config_check())\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_006_csr_search(self, mock_check):\n        \"\"\"CAhandler._config_check non existing request\"\"\"\n        mock_check.return_value = True\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertFalse(self.cahandler._csr_search(\"name\", \"foo\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_007_csr_search(self, mock_check):\n        \"\"\"CAhandler._config_check existing request\"\"\"\n        mock_check.return_value = True\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertTrue(self.cahandler._csr_search(\"name\", \"test_request\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_008_csr_search(self, mock_check):\n        \"\"\"CAhandler._config_check existing request\"\"\"\n        mock_check.return_value = False\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._csr_search(\"name\", \"test_request\"))\n        self.assertIn(\n            \"WARNING:test_a2c:column: name not in view_requests table\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_cert_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    def test_009_ca_load(self, mock_key, mock_cert):\n        \"\"\"CAhandler._ca_load for both cert and key\"\"\"\n        mock_key.return_value = \"key\"\n        mock_cert.return_value = (\"cert\", 1)\n        self.assertEqual((\"key\", \"cert\", 1), self.cahandler._ca_load())\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_cert_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    def test_010_ca_load(self, mock_key, mock_cert):\n        \"\"\"CAhandler._ca_load for cert only\"\"\"\n        mock_key.return_value = None\n        mock_cert.return_value = (\"cert\", 1)\n        self.assertEqual((None, \"cert\", 1), self.cahandler._ca_load())\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_cert_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    def test_011_ca_load(self, mock_key, mock_cert):\n        \"\"\"CAhandler._ca_load for cert only\"\"\"\n        mock_key.return_value = \"key\"\n        mock_cert.return_value = (None, None)\n        self.assertEqual((\"key\", None, None), self.cahandler._ca_load())\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_cert_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    def test_012_ca_load(self, mock_key, mock_cert):\n        \"\"\"CAhandler._ca_load without key and cert\"\"\"\n        mock_key.return_value = None\n        mock_cert.return_value = (None, None)\n        self.assertEqual((None, None, None), self.cahandler._ca_load())\n\n    def test_013_ca_cert_load(self):\n        \"\"\"CAhandler._ca_cert_load\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        self.assertTrue(self.cahandler._ca_cert_load())\n\n    def test_014_ca_cert_load(self):\n        \"\"\"CAhandler._ca_cert_load for non existing cert\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"bar\"\n        self.assertEqual((None, None), self.cahandler._ca_cert_load())\n\n    def test_015_ca_key_load(self):\n        \"\"\"CAhandler._ca_key_load\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_key = \"sub-ca\"\n        self.cahandler.passphrase = \"test1234\"\n        self.assertTrue(self.cahandler._ca_key_load())\n\n    def test_016_ca_key_load(self):\n        \"\"\"CAhandler._ca_key_load with wrong passphrase\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        self.cahandler.passphrase = \"wrongpw\"\n        self.assertFalse(self.cahandler._ca_key_load())\n\n    def test_017_ca_key_load(self):\n        \"\"\"CAhandler._ca_key_load without passphrase (should fail)\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        # self.cahandler.passphrase = 'wrongpw'\n        self.assertFalse(self.cahandler._ca_key_load())\n\n    @patch(\"cryptography.hazmat.primitives.serialization.load_pem_private_key\")\n    def test_018_ca_key_load(self, mock_key):\n        \"\"\"CAhandler._ca_key_load\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_key = \"sub-ca\"\n        self.cahandler.passphrase = \"test1234\"\n        mock_key.side_effect = Exception(\"exc_key_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._ca_key_load()\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load CA private key from database: exc_key_load\",\n            lcm.output,\n        )\n\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    def test_019_ca_cert_load(self, mock_certload):\n        \"\"\"CAhandler._ca_cert_load\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        mock_certload.side_effect = Exception(\"exc_cert_load\")\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual((None, None, None), self.cahandler._ca_load())\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to load CA certificate from database: exc_cert_load\",\n            lcm.output,\n        )\n\n    def test_020_csr_insert(self):\n        \"\"\"CAhandler._csr_insert empty item dic\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_021_csr_insert(self):\n        \"\"\"CAhandler._csr_insert full item dic\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"item\": 2, \"signed\": 0, \"request\": \"request\"}\n        self.assertEqual(2, self.cahandler._csr_insert(csr_dic))\n\n    def test_022_csr_insert(self):\n        \"\"\"CAhandler._csr_insert full item dic item has wrong datatype\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"item\": \"2\", \"signed\": 0, \"request\": \"request\"}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_023_csr_insert(self):\n        \"\"\"CAhandler._csr_insert full item dic item has wrong datatype\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"item\": 2, \"signed\": \"0\", \"request\": \"request\"}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_024_csr_insert(self):\n        \"\"\"CAhandler._csr_insert item dic without item\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"signed\": 0, \"request\": \"request\"}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_025_csr_insert(self):\n        \"\"\"CAhandler._csr_insert item dic without signed\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"item\": 2, \"request\": \"request\"}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_026_csr_insert(self):\n        \"\"\"CAhandler._csr_insert item dic without request\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        csr_dic = {\"item\": 2, \"signed\": 0}\n        self.assertFalse(self.cahandler._csr_insert(csr_dic))\n\n    def test_027_item_insert(self):\n        \"\"\"CAhandler._item_insert empty item dic\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_028_item_insert(self):\n        \"\"\"CAhandler._item_insert full item dic\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\n            \"name\": \"name\",\n            \"type\": 2,\n            \"source\": 0,\n            \"date\": \"date\",\n            \"comment\": \"comment\",\n        }\n        self.assertEqual(15, self.cahandler._item_insert(item_dic))\n\n    def test_029_item_insert(self):\n        \"\"\"CAhandler._item_insert no name\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\"type\": 2, \"source\": 0, \"date\": \"date\", \"comment\": \"comment\"}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_030_item_insert(self):\n        \"\"\"CAhandler._item_insert no type\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\"name\": \"name\", \"source\": 0, \"date\": \"date\", \"comment\": \"comment\"}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_031_item_insert(self):\n        \"\"\"CAhandler._item_insert no siurce\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\"name\": \"name\", \"item\": 2, \"date\": \"date\", \"comment\": \"comment\"}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_032_item_insert(self):\n        \"\"\"CAhandler._item_insert no date\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\"name\": \"name\", \"type\": 2, \"source\": 0, \"comment\": \"comment\"}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_033_item_insert(self):\n        \"\"\"CAhandler._item_insert no date\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\"name\": \"name\", \"type\": 2, \"source\": 0, \"date\": \"date\"}\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_034_item_insert(self):\n        \"\"\"CAhandler._item_insert full item dic type has wrong datatype\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\n            \"name\": \"name\",\n            \"type\": \"2\",\n            \"source\": 0,\n            \"date\": \"date\",\n            \"comment\": \"comment\",\n        }\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    def test_035_item_insert(self):\n        \"\"\"CAhandler._item_insert full item dic source has wrong datatype\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.issuing_ca_name = \"sub-ca\"\n        item_dic = {\n            \"name\": \"name\",\n            \"type\": 2,\n            \"source\": \"0\",\n            \"date\": \"date\",\n            \"comment\": \"comment\",\n        }\n        self.assertFalse(self.cahandler._item_insert(item_dic))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_search\")\n    def test_036_csr_import(self, mock_search):\n        \"\"\"CAhandler._csr_import with existing cert_dic\"\"\"\n        mock_search.return_value = {\"foo\", \"bar\"}\n        self.assertEqual(\n            {\"foo\", \"bar\"}, self.cahandler._csr_import(\"csr\", \"request_name\")\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._item_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_search\")\n    def test_037_csr_import(self, mock_search, mock_csr_insert, mock_item_insert):\n        \"\"\"CAhandler._csr_import with existing cert_dic\"\"\"\n        mock_search.return_value = {}\n        mock_csr_insert.return_value = 5\n        mock_item_insert.return_value = 10\n        self.assertEqual(\n            {\"item\": 10, \"signed\": 1, \"request\": \"csr\"},\n            self.cahandler._csr_import(\"csr\", \"request_name\"),\n        )\n\n    def test_038_cert_insert(self):\n        \"\"\"CAhandler._csr_import with empty cert_dic\"\"\"\n        cert_dic = {}\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_039_cert_insert(self):\n        \"\"\"CAhandler._csr_import item missing\"\"\"\n        cert_dic = {\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_040_cert_insert(self):\n        \"\"\"CAhandler._csr_import serial missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_041_cert_insert(self):\n        \"\"\"CAhandler._csr_import issuer missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_042_cert_insert(self):\n        \"\"\"CAhandler._csr_import ca missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_043_cert_insert(self):\n        \"\"\"CAhandler._csr_import cert missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_044_cert_insert(self):\n        \"\"\"CAhandler._csr_import iss_hash missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_045_cert_insert(self):\n        \"\"\"CAhandler._csr_import hash missing\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_046_cert_insert(self):\n        \"\"\"CAhandler._csr_import with item not int\"\"\"\n        cert_dic = {\n            \"item\": \"item\",\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_047_cert_insert(self):\n        \"\"\"CAhandler._csr_import with issuer not int\"\"\"\n        cert_dic = {\n            \"item\": 1,\n            \"serial\": \"serial\",\n            \"issuer\": \"issuer\",\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_048_cert_insert(self):\n        \"\"\"CAhandler._csr_import with ca not int\"\"\"\n        cert_dic = {\n            \"item\": 1,\n            \"serial\": \"serial\",\n            \"issuer\": 1,\n            \"ca\": \"ca\",\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_049_cert_insert(self):\n        \"\"\"CAhandler._csr_import with iss_hash not int\"\"\"\n        cert_dic = {\n            \"item\": 1,\n            \"serial\": \"serial\",\n            \"issuer\": 2,\n            \"ca\": 3,\n            \"cert\": \"cert\",\n            \"iss_hash\": \"iss_hash\",\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    def test_050_cert_insert(self):\n        \"\"\"CAhandler._csr_import with hash not int\"\"\"\n        cert_dic = {\n            \"item\": 1,\n            \"serial\": \"serial\",\n            \"issuer\": 2,\n            \"ca\": 3,\n            \"cert\": \"cert\",\n            \"iss_hash\": 4,\n            \"hash\": \"hash\",\n        }\n        self.assertFalse(self.cahandler._cert_insert(cert_dic))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_051_cert_insert(self, mock_open, mock_close):\n        \"\"\"CAhandler._csr_import with hash not int\"\"\"\n        cert_dic = {\n            \"item\": 1,\n            \"serial\": \"serial\",\n            \"issuer\": 2,\n            \"ca\": 3,\n            \"cert\": \"cert\",\n            \"iss_hash\": 4,\n            \"hash\": 5,\n        }\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.cahandler.cursor.lastrowid = 5\n        self.assertEqual(5, self.cahandler._cert_insert(cert_dic))\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n\n    def test_052_pemcertchain_generate(self):\n        \"\"\"CAhandler._pemcertchain_generate no certificates\"\"\"\n        ee_cert = None\n        issuer_cert = None\n        self.cahandler.ca_cert_chain_list = []\n        self.assertFalse(self.cahandler._pemcertchain_generate(ee_cert, issuer_cert))\n\n    def test_053_pemcertchain_generate(self):\n        \"\"\"CAhandler._pemcertchain_generate no issuer\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = None\n        self.cahandler.ca_cert_chain_list = []\n        self.assertEqual(\n            \"ee_cert\", self.cahandler._pemcertchain_generate(ee_cert, issuer_cert)\n        )\n\n    def test_054_pemcertchain_generate(self):\n        \"\"\"CAhandler._pemcertchain_generate no ca chain\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = \"issuer_cert\"\n        self.cahandler.ca_cert_chain_list = []\n        self.assertEqual(\n            \"ee_certissuer_cert\",\n            self.cahandler._pemcertchain_generate(ee_cert, issuer_cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_search\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    def test_055_pemcertchain_generate(self, mock_cert, mock_search):\n        \"\"\"CAhandler._pemcertchain_generate empty cert dic in ca_chain\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = \"issuer_cert\"\n        self.cahandler.ca_cert_chain_list = [\"foo_bar\"]\n        mock_search.return_value = None\n        mock_cert.side_effect = [\"foo\", \"bar\"]\n        self.assertEqual(\n            \"ee_certissuer_cert\",\n            self.cahandler._pemcertchain_generate(ee_cert, issuer_cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_search\")\n    @patch(\"OpenSSL.crypto.load_certificate\")\n    def test_056_pemcertchain_generate(self, mock_cert, mock_search):\n        \"\"\"CAhandler._pemcertchain_generate empty no cert in chain\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = \"issuer_cert\"\n        self.cahandler.ca_cert_chain_list = [\"foo_bar\"]\n        mock_search.return_value = {\"foo\", \"bar\"}\n        mock_cert.side_effect = [\"foo\", \"bar\"]\n        self.assertEqual(\n            \"ee_certissuer_cert\",\n            self.cahandler._pemcertchain_generate(ee_cert, issuer_cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_search\")\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    def test_057_pemcertchain_generate(self, mock_load, mock_search, mock_b64dec):\n        \"\"\"CAhandler._pemcertchain_generate one cert in chain\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = \"issuer_cert\"\n        self.cahandler.ca_cert_chain_list = [\"foo_bar\"]\n        mock_search.return_value = {\"cert\": \"foo\"}\n        mock_load.return_value = Mock()\n        mock_load.return_value.public_bytes.side_effect = [\"foo1\", \"foo2\"]\n        mock_b64dec.return_value = \"b64dec\"\n        self.assertEqual(\n            \"ee_certissuer_certfoo1\",\n            self.cahandler._pemcertchain_generate(ee_cert, issuer_cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_decode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_search\")\n    @patch(\"cryptography.x509.load_der_x509_certificate\")\n    def test_058_pemcertchain_generate(self, mock_load, mock_search, mock_b64dec):\n        \"\"\"CAhandler._pemcertchain_generate two certs in chain\"\"\"\n        ee_cert = \"ee_cert\"\n        issuer_cert = \"issuer_cert\"\n        self.cahandler.ca_cert_chain_list = [\"foo_bar\", \"foo_bar\"]\n        mock_search.return_value = {\"cert\": \"foo\"}\n        mock_load.return_value = Mock()\n        mock_load.return_value.public_bytes.side_effect = [\"foo1\", \"foo2\"]\n        mock_b64dec.return_value = \"b64dec\"\n        self.assertEqual(\n            \"ee_certissuer_certfoo1foo2\",\n            self.cahandler._pemcertchain_generate(ee_cert, issuer_cert),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_059_requestname_get(self, mock_cn):\n        \"\"\"CAhandler._requestname_get from cn\"\"\"\n        mock_cn.return_value = \"foo\"\n        self.assertEqual(\"foo\", self.cahandler._requestname_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_060_requestname_get(self, mock_cn, mock_san):\n        \"\"\"CAhandler._requestname_get empty cn empty san\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = []\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._requestname_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to split SAN from CSR subjectAltName: []\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_061_requestname_get(self, mock_cn, mock_san):\n        \"\"\"CAhandler._requestname_get empty cn empty dsmaged san\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"foo\"]\n        self.assertFalse(self.cahandler._requestname_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_062_requestname_get(self, mock_cn, mock_san):\n        \"\"\"CAhandler._requestname_get empty cn empty dsmaged san\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"dns:foo\"]\n        self.assertEqual(\"foo\", self.cahandler._requestname_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_063_requestname_get(self, mock_cn, mock_san):\n        \"\"\"CAhandler._requestname_get empty cn empty damaged san\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"dns:foo\", \"bar\"]\n        self.assertEqual(\"foo\", self.cahandler._requestname_get(\"csr\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_san_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.csr_cn_get\")\n    def test_064_requestname_get(self, mock_cn, mock_san):\n        \"\"\"CAhandler._requestname_get empty cn empty damaged san\"\"\"\n        mock_cn.return_value = None\n        mock_san.return_value = [\"foo\", \"bar\"]\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._requestname_get(\"csr\"))\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to split SAN from CSR subjectAltName: ['foo', 'bar']\",\n            lcm.output,\n        )\n\n    def test_065_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert with empty rev_dic\"\"\"\n        rev_dic = {}\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_066_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert no caID\"\"\"\n        rev_dic = {\n            \"serial\": \"serial\",\n            \"date\": \"date\",\n            \"invaldate\": \"invaldate\",\n            \"reasonBit\": 0,\n        }\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_067_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert no serial\"\"\"\n        rev_dic = {\"caID\": 4, \"date\": \"date\", \"invaldate\": \"invaldate\", \"reasonBit\": 0}\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_068_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert no date\"\"\"\n        rev_dic = {\n            \"caID\": 4,\n            \"serial\": \"serial\",\n            \"invaldate\": \"invaldate\",\n            \"reasonBit\": 0,\n        }\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_069_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert no invaldate\"\"\"\n        rev_dic = {\"caID\": 4, \"serial\": \"serial\", \"date\": \"date\", \"reasonBit\": 0}\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_070_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert no resonBit\"\"\"\n        rev_dic = {\n            \"caID\": 4,\n            \"serial\": \"serial\",\n            \"date\": \"date\",\n            \"invaldate\": \"invaldate\",\n        }\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_071_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert with caID is not int\"\"\"\n        rev_dic = {\n            \"caID\": \"caID\",\n            \"serial\": \"serial\",\n            \"date\": \"date\",\n            \"invaldate\": \"invaldate\",\n            \"reasonBit\": 0,\n        }\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    def test_072_cert_insert(self):\n        \"\"\"CAhandler._revocation_insert with caID is not int\"\"\"\n        rev_dic = {\n            \"caID\": 0,\n            \"serial\": \"serial\",\n            \"date\": \"date\",\n            \"invaldate\": \"invaldate\",\n            \"reasonBit\": \"0\",\n        }\n        self.assertFalse(self.cahandler._revocation_insert(rev_dic))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_073_rev_insert(self, mock_open, mock_close):\n        \"\"\"CAhandler._revocation_insert with caID is not inall okt\"\"\"\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.cahandler.cursor.lastrowid = 5\n        rev_dic = {\n            \"caID\": 0,\n            \"serial\": \"serial\",\n            \"date\": \"date\",\n            \"invaldate\": \"invaldate\",\n            \"reasonBit\": 0,\n        }\n        self.assertEqual(5, self.cahandler._revocation_insert(rev_dic))\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_074_revoke(self, mock_date):\n        \"\"\"CAhandler.revocation without xdb file\"\"\"\n        mock_date.return_value = \"foo\"\n        self.assertEqual(\n            (500, \"urn:ietf:params:acme:error:serverInternal\", \"configuration error\"),\n            self.cahandler.revoke(\"cert\", \"reason\", None),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_075_revoke(self, mock_date, mock_ca, mock_serial):\n        \"\"\"CAhandler.revocation no CA ID\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", None)\n        mock_serial.return_value = 1000\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"certificate lookup failed\",\n            ),\n            self.cahandler.revoke(\"cert\", \"reason\", None),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_076_revoke(self, mock_date, mock_ca, mock_serial):\n        \"\"\"CAhandler.revocation no serial\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", 2)\n        mock_serial.return_value = None\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"certificate lookup failed\",\n            ),\n            self.cahandler.revoke(\"cert\", \"reason\", None),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_search\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_077_revoke(\n        self, mock_date, mock_ca, mock_serial, mock_rev_insert, mock_search\n    ):\n        \"\"\"CAhandler.revocation no serial\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", 2)\n        mock_search.return_value = None\n        mock_rev_insert.return_value = None\n        mock_serial.return_value = 1000\n        self.assertEqual(\n            (\n                500,\n                \"urn:ietf:params:acme:error:serverInternal\",\n                \"database update failed\",\n            ),\n            self.cahandler.revoke(\"cert\", \"reason\", None),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_search\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_078_revoke(\n        self, mock_date, mock_ca, mock_serial, mock_rev_insert, mock_search\n    ):\n        \"\"\"CAhandler.revocation no serial\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", 2)\n        mock_search.return_value = \"foo\"\n        mock_rev_insert.return_value = 20\n        mock_serial.return_value = 1000\n        self.assertEqual(\n            (\n                400,\n                \"urn:ietf:params:acme:error:alreadyRevoked\",\n                \"Certificate has already been revoked\",\n            ),\n            self.cahandler.revoke(\"cert\", \"reason\", None),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_search\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_079_revoke(\n        self, mock_date, mock_ca, mock_serial, mock_rev_insert, mock_search, mock_eab\n    ):\n        \"\"\"CAhandler.revocation no serial\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", 2)\n        mock_search.return_value = None\n        mock_rev_insert.return_value = 20\n        mock_serial.return_value = 1000\n        self.assertEqual(\n            (200, None, None), self.cahandler.revoke(\"cert\", \"reason\", None)\n        )\n        self.assertFalse(mock_eab.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_revocation_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_search\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._revocation_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.cert_serial_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.uts_to_date_utc\")\n    def test_080_revoke(\n        self, mock_date, mock_ca, mock_serial, mock_rev_insert, mock_search, mock_eab\n    ):\n        \"\"\"CAhandler.revocation no serial\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.eab_profiling = True\n        mock_date.return_value = \"foo\"\n        mock_ca.return_value = (\"key\", \"cert\", 2)\n        mock_search.return_value = None\n        mock_rev_insert.return_value = 20\n        mock_serial.return_value = 1000\n        self.assertEqual(\n            (200, None, None), self.cahandler.revoke(\"cert\", \"reason\", None)\n        )\n        self.assertTrue(mock_eab.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_081_cert_search(self, mock_check):\n        \"\"\"CAhandler._cert_sarch cert can be found\"\"\"\n        mock_check.return_value = True\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        search_result = {\n            \"item\": 6,\n            \"hash\": 1675584264,\n            \"iss_hash\": 1339028853,\n            \"serial\": \"0BCC30C544EF26A4\",\n            \"issuer\": 4,\n            \"ca\": 0,\n            \"cert\": \"MIIEQTCCAimgAwIBAgIIC8wwxUTvJqQwDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGc3ViLWNhMB4XDTIwMDYwOTE3MTkwMFoXDTIxMDYwOTE3MTkwMFowGzEZMBcGA1UEAxMQY2xpZW50LmJhci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJps2tk/d5pqv1gSeLnDBFQSzznY/iSBtzRNLlRWm6J7yOAERgGsbMBW7s5AhYRbuHuberlBtsyFyKenWvijo6r7DTOGiv2oBf7iCoCXYbNAqlvnP5inzp6ZmmgmxigLFbdlTfPQBkaytDzLAav1KLCmCof4DpQunsxdDjW0kBm8jRC7HY5bauxeFKQb2NcGmjlB3kQjZNHF52xG/GgkMIH7E0NJUhmsVfItSezkmFUQFhP2VqYYsiPRtvXlZqpzPISxn2InGcUaaBzJFO7RWif0IIsgzcyzqXvt8KEqeoI15gmd1G4lXPeyadXG8kzE8L+8f4J+gGgQSA1eR4VMkOMCAwEAAaOBkjCBjzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRjovc4aaN6LCIE5E/ZgsLBH+3/WDAOBgNVHQ8BAf8EBAMCA+gwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdEQQUMBKCEGNsaWVudC5iYXIubG9jYWwwEQYJYIZIAYb4QgEBBAQDAgZAMA0GCSqGSIb3DQEBCwUAA4ICAQCZm5d3jc9oopD193bGwJFo8NNo1wzYvvqbK/lONy/JsisX1pERxN+EZyTB2CLxQ4yKZU9Xnx0fmcJExqoPLEva6hAMdOiSEsEs52yyL6gjMLHxJJfdXBiqMZetp+BCPf23rc96ONzyjURDCfsN4VMg7090e9yKpuyHKIOHStqMT+ZLvPcd+YiU4jMazoagauEW2mdpqyA8mN92qiphwo8QMCv3XZJWJ1PEwaCTGhBxlzMoaknWKzCD2YQ/yyGE4Ha8vBaymk1eh7txo5B53C0OpO0UT4WGUOZDP1GPySymqQfDO6R9BhBjyggsG5G9FA84tUqZJAKlGhPesQyIQBM4SZlQTJt/hP/cCoZ6BiibBdaZnLzOyH+NTJ9ou0hpmMp2LZiB8G2Igam7wdXySvQe9sxXXDDTKhxwqk7V+by2gS6asfcQjstQQeMN/iMrg3AtZt/Kl5WcHcwSjZAypHugPiwjr48WHvDS2lUKnbbDuiCxvc1TsPGG6Z+b/0aTwrps6yMeTRuDk3A8DYceHftrWZSOgg+5A2ISd58vPOHiamATVLXGJ1vnCP0Sm/Z4QCnIGfOvxltdAnrcA75MnefaOmQv9CrhwyBembugd9fPC/uFi/ESKGPuo6zLYwjFwLqwNe99UgU98iYz9rfdKNqJ6fWRolzz4AXqUHQ4Dc8eZA==\",\n        }\n        self.assertEqual(search_result, self.cahandler._cert_search(\"name\", \"client\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_082_cert_search(self, mock_check):\n        \"\"\"CAhandler._cert_sarch cert failed\"\"\"\n        mock_check.return_value = True\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertFalse(self.cahandler._cert_search(\"name\", \"client_failed\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_083_cert_search(self, mock_check):\n        \"\"\"CAhandler._cert_sarch item search succ / cert_search failed\"\"\"\n        mock_check.return_value = True\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertFalse(self.cahandler._cert_search(\"name\", \"item_no_cert\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    def test_084_cert_search(self, mock_check):\n        \"\"\"CAhandler._cert_sarch cert can be found\"\"\"\n        mock_check.return_value = False\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        search_result = {\n            \"item\": 6,\n            \"hash\": 1675584264,\n            \"iss_hash\": 1339028853,\n            \"serial\": \"0BCC30C544EF26A4\",\n            \"issuer\": 4,\n            \"ca\": 0,\n            \"cert\": \"MIIEQTCCAimgAwIBAgIIC8wwxUTvJqQwDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGc3ViLWNhMB4XDTIwMDYwOTE3MTkwMFoXDTIxMDYwOTE3MTkwMFowGzEZMBcGA1UEAxMQY2xpZW50LmJhci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJps2tk/d5pqv1gSeLnDBFQSzznY/iSBtzRNLlRWm6J7yOAERgGsbMBW7s5AhYRbuHuberlBtsyFyKenWvijo6r7DTOGiv2oBf7iCoCXYbNAqlvnP5inzp6ZmmgmxigLFbdlTfPQBkaytDzLAav1KLCmCof4DpQunsxdDjW0kBm8jRC7HY5bauxeFKQb2NcGmjlB3kQjZNHF52xG/GgkMIH7E0NJUhmsVfItSezkmFUQFhP2VqYYsiPRtvXlZqpzPISxn2InGcUaaBzJFO7RWif0IIsgzcyzqXvt8KEqeoI15gmd1G4lXPeyadXG8kzE8L+8f4J+gGgQSA1eR4VMkOMCAwEAAaOBkjCBjzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRjovc4aaN6LCIE5E/ZgsLBH+3/WDAOBgNVHQ8BAf8EBAMCA+gwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdEQQUMBKCEGNsaWVudC5iYXIubG9jYWwwEQYJYIZIAYb4QgEBBAQDAgZAMA0GCSqGSIb3DQEBCwUAA4ICAQCZm5d3jc9oopD193bGwJFo8NNo1wzYvvqbK/lONy/JsisX1pERxN+EZyTB2CLxQ4yKZU9Xnx0fmcJExqoPLEva6hAMdOiSEsEs52yyL6gjMLHxJJfdXBiqMZetp+BCPf23rc96ONzyjURDCfsN4VMg7090e9yKpuyHKIOHStqMT+ZLvPcd+YiU4jMazoagauEW2mdpqyA8mN92qiphwo8QMCv3XZJWJ1PEwaCTGhBxlzMoaknWKzCD2YQ/yyGE4Ha8vBaymk1eh7txo5B53C0OpO0UT4WGUOZDP1GPySymqQfDO6R9BhBjyggsG5G9FA84tUqZJAKlGhPesQyIQBM4SZlQTJt/hP/cCoZ6BiibBdaZnLzOyH+NTJ9ou0hpmMp2LZiB8G2Igam7wdXySvQe9sxXXDDTKhxwqk7V+by2gS6asfcQjstQQeMN/iMrg3AtZt/Kl5WcHcwSjZAypHugPiwjr48WHvDS2lUKnbbDuiCxvc1TsPGG6Z+b/0aTwrps6yMeTRuDk3A8DYceHftrWZSOgg+5A2ISd58vPOHiamATVLXGJ1vnCP0Sm/Z4QCnIGfOvxltdAnrcA75MnefaOmQv9CrhwyBembugd9fPC/uFi/ESKGPuo6zLYwjFwLqwNe99UgU98iYz9rfdKNqJ6fWRolzz4AXqUHQ4Dc8eZA==\",\n        }\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._cert_search(\"name\", \"client\"))\n        self.assertIn(\n            \"WARNING:test_a2c:column: name not in items table\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_085_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - ca_chain is not json format\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"ca_cert_chain_list\": \"[foo]\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.ca_cert_chain_list)\n        self.assertIn(\n            'ERROR:test_a2c:Parameter \"ca_cert_chain_list\" cannot be loaded',\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_086_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"template_name\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo\", self.cahandler.template_name)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_087_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"xdb_file\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo\", self.cahandler.xdb_file)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_088_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"passphrase\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo\", self.cahandler.passphrase)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_089_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_name\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo\", self.cahandler.issuing_ca_name)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_090_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"issuing_ca_key\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo\", self.cahandler.issuing_ca_key)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_091_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with passphrase variable\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"passphrase_variable\": \"foo\"}\n        mock_load_cfg.return_value = parser\n        self.cahandler._config_load()\n        self.assertEqual(\"foo_var\", self.cahandler.passphrase)\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_092_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template passpharese variable configured but does not exist\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"passphrase_variable\": \"does_not_exist\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertFalse(self.cahandler.passphrase)\n        self.assertIn(\n            \"ERROR:test_a2c:Could not load passphrase_variable:'does_not_exist'\",\n            lcm.output,\n        )\n\n    @patch.dict(\"os.environ\", {\"foo\": \"foo_var\"})\n    @patch(\"examples.ca_handler.xca_ca_handler.load_config\")\n    def test_093_config_load(self, mock_load_cfg):\n        \"\"\"test _config_load - load template with passphrase variable  - overwritten bei cfg file\"\"\"\n        parser = configparser.ConfigParser()\n        parser[\"CAhandler\"] = {\"passphrase_variable\": \"foo\", \"passphrase\": \"foo_file\"}\n        mock_load_cfg.return_value = parser\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.cahandler._config_load()\n        self.assertEqual(\"foo_file\", self.cahandler.passphrase)\n        self.assertIn(\n            \"INFO:test_a2c:Overwrite passphrase_variable\",\n            lcm.output,\n        )\n\n    def test_094_stream_split(self):\n        \"\"\"test stream_split - all ok\"\"\"\n        byte_stream = b\"before\\x00\\x00\\x00\\x0cafter\"\n        self.assertEqual(\n            (b\"before\\x00\\x00\\x00\\x0c\", b\"after\"),\n            self.cahandler._stream_split(byte_stream),\n        )\n\n    def test_095_stream_split(self):\n        \"\"\"test stream_split - no bytestream\"\"\"\n        byte_stream = None\n        self.assertEqual((None, None), self.cahandler._stream_split(byte_stream))\n\n    def test_096_stream_split(self):\n        \"\"\"test stream_split - no match\"\"\"\n        byte_stream = b\"foofoobar\"\n        self.assertEqual((None, None), self.cahandler._stream_split(byte_stream))\n\n    def test_097_stream_split(self):\n        \"\"\"test stream_split - start with match match\"\"\"\n        byte_stream = b\"\\x00\\x00\\x00\\x0cafter\"\n        self.assertEqual(\n            (b\"\\x00\\x00\\x00\\x0c\", b\"after\"), self.cahandler._stream_split(byte_stream)\n        )\n\n    def test_098__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - all ok\"\"\"\n        utf_stream = b\"foo\\x00\\x00\\x00bar\"\n        self.assertEqual(({\"foo\": \"ar\"}), self.cahandler._utf_stream_parse(utf_stream))\n\n    def test_099__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - two parameter\"\"\"\n        utf_stream = b\"foo1\\x00\\x00\\x00_bar1\\x00\\x00\\x00_foo2\\x00\\x00\\x00_bar2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\", \"foo2\": \"bar2\"}),\n            self.cahandler._utf_stream_parse(utf_stream),\n        )\n\n    def test_100__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - non even parameter\"\"\"\n        utf_stream = b\"foo1\\x00\\x00\\x00_bar1\\x00\\x00\\x00_foo2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n        )\n\n    def test_101__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - replace single \\x00 in list key\"\"\"\n        utf_stream = b\"f\\x00oo1\\x00\\x00\\x00_bar1\\x00\\x00\\x00_foo2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n        )\n\n    def test_102__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - replace multiple \\x00 in list key\"\"\"\n        utf_stream = b\"f\\x00o\\x00o\\x001\\x00\\x00\\x00_bar1\\x00\\x00\\x00_foo2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n        )\n\n    def test_103__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - replace single \\x00 in list value\"\"\"\n        utf_stream = b\"foo1\\x00\\x00\\x00_b\\x00ar1\\x00\\x00\\x00_foo2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n        )\n\n    def test_104__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - replace multiple \\x00 in list value\"\"\"\n        utf_stream = b\"foo\\x001\\x00\\x00\\x00_b\\x00a\\x00r1\\x00\\x00\\x00_foo2\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n        )\n\n    def test_105__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - no utf_stream\"\"\"\n        utf_stream = None\n        self.assertFalse(self.cahandler._utf_stream_parse(utf_stream))\n\n    def test_106__utf_stream_parse(self):\n        \"\"\"test _utf_stream_parse()  - skip template with empty eku\"\"\"\n        utf_stream = b\"foo1\\x00\\x00\\x00_bar1\\x00\\x00\\x00_foo2\\x00\\x00\\x00_eKeyUse\\xff\\xff\\xff\\xff\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                ({\"foo1\": \"bar1\"}), self.cahandler._utf_stream_parse(utf_stream)\n            )\n        self.assertIn(\n            \"INFO:test_a2c:Hack to skip template with empty eku - maybe a bug in xca...\",\n            lcm.output,\n        )\n\n    def test_107__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - with country\"\"\"\n        asn1_stream = b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\"\n        self.assertEqual(\n            ({\"countryName\": \"co\"}), self.cahandler._asn1_stream_parse(asn1_stream)\n        )\n\n    def test_108__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - country, loc\"\"\"\n        asn1_stream = (\n            b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\\x06\\x03\\x55\\x04\\x07\\03floc\"\n        )\n        self.assertEqual(\n            ({\"countryName\": \"co\", \"localityName\": \"loc\"}),\n            self.cahandler._asn1_stream_parse(asn1_stream),\n        )\n\n    def test_109__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - country, lo, state\"\"\"\n        asn1_stream = b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\\x06\\x03\\x55\\x04\\x07\\03floc\\x06\\x03\\x55\\x04\\x08\\05fstate\"\n        self.assertEqual(\n            (\n                {\n                    \"countryName\": \"co\",\n                    \"localityName\": \"loc\",\n                    \"stateOrProvinceName\": \"state\",\n                }\n            ),\n            self.cahandler._asn1_stream_parse(asn1_stream),\n        )\n\n    def test_110__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - country, loc, state, org\"\"\"\n        asn1_stream = b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\\x06\\x03\\x55\\x04\\x07\\03floc\\x06\\x03\\x55\\x04\\x08\\05fstate\\x06\\x03\\x55\\x04\\x0a\\03forg\"\n        self.assertEqual(\n            (\n                {\n                    \"countryName\": \"co\",\n                    \"localityName\": \"loc\",\n                    \"stateOrProvinceName\": \"state\",\n                    \"organizationName\": \"org\",\n                }\n            ),\n            self.cahandler._asn1_stream_parse(asn1_stream),\n        )\n\n    def test_111__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - country, loc, state, org, ou\"\"\"\n        asn1_stream = b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\\x06\\x03\\x55\\x04\\x07\\03floc\\x06\\x03\\x55\\x04\\x08\\05fstate\\x06\\x03\\x55\\x04\\x0a\\03forg\\x06\\x03\\x55\\x04\\x0b\\02fou\"\n        self.assertEqual(\n            (\n                {\n                    \"countryName\": \"co\",\n                    \"localityName\": \"loc\",\n                    \"stateOrProvinceName\": \"state\",\n                    \"organizationName\": \"org\",\n                    \"organizationalUnitName\": \"ou\",\n                }\n            ),\n            self.cahandler._asn1_stream_parse(asn1_stream),\n        )\n\n    def test_112__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - extralong value\"\"\"\n        asn1_stream = b\"12345678foo\\x06\\x03\\x55\\x04\\x07\\x11flllllllllllllllll\"\n        self.assertEqual(\n            ({\"localityName\": \"lllllllllllllllll\"}),\n            self.cahandler._asn1_stream_parse(asn1_stream),\n        )\n\n    def test_113__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse - empty stream\"\"\"\n        asn1_stream = None\n        self.assertFalse(self.cahandler._asn1_stream_parse(asn1_stream))\n\n    def test_114__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse - too short\"\"\"\n        asn1_stream = b\"123456\"\n        self.assertFalse(self.cahandler._asn1_stream_parse(asn1_stream))\n\n    def test_115__ans1_stream_parse(self):\n        \"\"\"test _ans1_stream_parse  - country, non existing value in beteeen\"\"\"\n        asn1_stream = (\n            b\"12345678foo\\x06\\x03\\x55\\x04\\x06\\02fco\\x06\\x03\\x55\\x05\\x07\\03floc\"\n        )\n        self.assertEqual(\n            ({\"countryName\": \"co\"}), self.cahandler._asn1_stream_parse(asn1_stream)\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._validity_calculate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._utf_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._asn1_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_116__template_parse(self, mock_split, mock_asn, mock_utf, mock_valid):\n        \"\"\"__template_parse() - all good\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (b\"foo\", b\"bar\")\n        mock_asn.return_value = {\"foo1\": \"bar1\"}\n        mock_utf.return_value = {\"foo2\": \"bar2\"}\n        mock_valid.return_value = \"valid\"\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}, {\"foo2\": \"bar2\", \"validity\": \"valid\"}),\n            self.cahandler._template_parse(byte_string),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._validity_calculate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._utf_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._asn1_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_117__template_parse(self, mock_split, mock_asn, mock_utf, mock_valid):\n        \"\"\"__template_parse() - multiple values\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (b\"foo\", b\"bar\")\n        mock_asn.return_value = {\"foo1\": \"bar1\", \"foo11\": \"bar11\"}\n        mock_utf.return_value = {\"foo2\": \"bar2\", \"foo21\": \"bar21\"}\n        mock_valid.return_value = \"valid\"\n        self.assertEqual(\n            (\n                {\"foo1\": \"bar1\", \"foo11\": \"bar11\"},\n                {\"foo2\": \"bar2\", \"foo21\": \"bar21\", \"validity\": \"valid\"},\n            ),\n            self.cahandler._template_parse(byte_string),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._validity_calculate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._utf_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_118__template_parse(self, mock_split, mock_utf, mock_valid):\n        \"\"\"__template_parse() - no asn1_stream returned\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (None, b\"bar\")\n        mock_utf.return_value = {\"foo2\": \"bar2\"}\n        mock_valid.return_value = \"valid\"\n        self.assertEqual(\n            ({}, {\"foo2\": \"bar2\", \"validity\": \"valid\"}),\n            self.cahandler._template_parse(byte_string),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._asn1_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_119__template_parse(self, mock_split, mock_asn):\n        \"\"\"__template_parse() - no asn1_stream returned\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (b\"foo\", None)\n        mock_asn.return_value = {\"foo1\": \"bar1\"}\n        self.assertEqual(\n            ({\"foo1\": \"bar1\"}, {}), self.cahandler._template_parse(byte_string)\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_120__template_parse(self, mock_split):\n        \"\"\"__template_parse() - no asn1_stream returned\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (None, None)\n        self.assertEqual(({}, {}), self.cahandler._template_parse(byte_string))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._validity_calculate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._utf_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._asn1_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_121__template_parse(self, mock_split, mock_asn, mock_utf, mock_valid):\n        \"\"\"__template_parse() - multiple values replace blank with None\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (b\"foo\", b\"bar\")\n        mock_asn.return_value = {\"foo1\": \"bar1\", \"foo11\": \"bar11\"}\n        mock_utf.return_value = {\"foo2\": \"bar2\", \"foo21\": \"\"}\n        mock_valid.return_value = \"valid\"\n        self.assertEqual(\n            (\n                {\"foo1\": \"bar1\", \"foo11\": \"bar11\"},\n                {\"foo2\": \"bar2\", \"foo21\": None, \"validity\": \"valid\"},\n            ),\n            self.cahandler._template_parse(byte_string),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._validity_calculate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._utf_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._asn1_stream_parse\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._stream_split\")\n    def test_122__template_parse(self, mock_split, mock_asn, mock_utf, mock_valid):\n        \"\"\"__template_parse() - multiple values replace blanks with None\"\"\"\n        byte_string = \"foo\"\n        mock_split.return_value = (b\"foo\", b\"bar\")\n        mock_asn.return_value = {\"foo1\": \"bar1\", \"foo11\": \"bar11\"}\n        mock_utf.return_value = {\"foo2\": \"bar2\", \"foo21\": \"\", \"foo22\": \"\"}\n        mock_valid.return_value = \"valid\"\n        self.assertEqual(\n            (\n                {\"foo1\": \"bar1\", \"foo11\": \"bar11\"},\n                {\"foo2\": \"bar2\", \"foo21\": None, \"foo22\": None, \"validity\": \"valid\"},\n            ),\n            self.cahandler._template_parse(byte_string),\n        )\n\n    def test_123__template_load(self):\n        \"\"\"CAhandler._templatelod - existing template\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.template_name = \"template\"\n        dn_dic = {\n            \"countryName\": \"co\",\n            \"stateOrProvinceName\": \"prov\",\n            \"localityName\": \"loc\",\n            \"organizationName\": \"org\",\n            \"organizationalUnitName\": \"ou\",\n        }\n        # template_dic = {'validity': 30, 'validN': '30', 'validMidn': '0', 'validM': '0', 'subKey': '0', 'subAltName': None, 'nsSslServerName': None, 'nsRevocationUrl': None, 'nsRenewalUrl': None, 'nsComment': 'xca certificate', 'nsCertType': '0', 'nsCaPolicyUrl': None, 'nsCARevocationUrl': None, 'nsBaseUrl': None, 'noWellDefinedExpDate': '0', 'kuCritical': '1', 'keyUse': '3', 'issAltName': None, 'ekuCritical': '1', 'eKeyUse': 'serverAuth, clientAuth', 'crlDist': None, 'ca': '0', 'bcCritical': '0', 'basicPath': None, 'authKey': '0', 'authInfAcc': None, 'adv_ext': None}\n        template_dic = {\n            \"validN\": \"30\",\n            \"validMidn\": \"0\",\n            \"validM\": \"0\",\n            \"subKey\": \"0\",\n            \"subAltName\": None,\n            \"nsSslServerName\": None,\n            \"nsRevocationUrl\": None,\n            \"nsRenewalUrl\": None,\n            \"nsComment\": \"xca certificate\",\n            \"nsCertType\": \"0\",\n            \"nsCaPolicyUrl\": None,\n            \"nsCARevocationUrl\": None,\n            \"nsBaseUrl\": None,\n            \"noWellDefinedExpDate\": \"0\",\n            \"kuCritical\": \"1\",\n            \"keyUse\": \"3\",\n            \"issAltName\": None,\n            \"ekuCritical\": \"1\",\n            \"eKeyUse\": \"clientAuth, codeSigning\",\n            \"crlDist\": None,\n            \"ca\": \"0\",\n            \"bcCritical\": \"0\",\n            \"basicPath\": None,\n            \"authKey\": \"0\",\n            \"authInfAcc\": None,\n            \"adv_ext\": None,\n            \"OCSPstaple\": \"0\",\n            \"validity\": 30,\n        }\n        self.assertEqual((dn_dic, template_dic), self.cahandler._template_load())\n\n    def test_124__template_load(self):\n        \"\"\"CAhandler._templatelod - not existing template\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.cahandler.template_name = \"notexist\"\n        self.assertEqual(({}, {}), self.cahandler._template_load())\n\n    def test_125__validity_calculate(self):\n        \"\"\"CAhandler._validity_calculate() - day value\"\"\"\n        template_dic = {\"validM\": \"0\", \"validN\": \"10\"}\n        self.assertEqual(10, self.cahandler._validity_calculate(template_dic))\n\n    def test_126__validity_calculate(self):\n        \"\"\"CAhandler._validity_calculate() - month value\"\"\"\n        template_dic = {\"validM\": \"1\", \"validN\": \"10\"}\n        self.assertEqual(300, self.cahandler._validity_calculate(template_dic))\n\n    def test_127__validity_calculate(self):\n        \"\"\"CAhandler._validity_calculate() - year value\"\"\"\n        template_dic = {\"validM\": \"2\", \"validN\": \"2\"}\n        self.assertEqual(730, self.cahandler._validity_calculate(template_dic))\n\n    def test_128__validity_calculate(self):\n        \"\"\"CAhandler._validity_calculate() - novalidn\"\"\"\n        template_dic = {\"validM\": \"2\", \"novalidN\": \"2\"}\n        self.assertEqual(365, self.cahandler._validity_calculate(template_dic))\n\n    def test_129__validity_calculate(self):\n        \"\"\"CAhandler._validity_calculate() - novalidn\"\"\"\n        template_dic = {\"novalidM\": \"2\", \"validN\": \"2\"}\n        self.assertEqual(365, self.cahandler._validity_calculate(template_dic))\n\n    def test_130__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup 0 defaulting to 23\"\"\"\n        kup = 0\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": True,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": True,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(result, self.cahandler._kue_generate(kup))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with value 23\", lcm.output\n        )\n\n    def test_131__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup '0' defaulting to 23\"\"\"\n        kup = \"0\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": True,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": True,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(result, self.cahandler._kue_generate(kup))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with value 23\", lcm.output\n        )\n\n    def test_132__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup cannot get converted to int\"\"\"\n        kup = \"a\"\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": True,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": True,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(result, self.cahandler._kue_generate(kup))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with value 23\", lcm.output\n        )\n\n    def test_133__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup none\"\"\"\n        kup = None\n        result = {\n            \"digital_signature\": True,\n            \"content_commitment\": True,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": True,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(result, self.cahandler._kue_generate(kup))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with value 23\", lcm.output\n        )\n\n    def test_134__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup none but csr_extensions\"\"\"\n        kup = None\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(\"ku_csr\", self.cahandler._kue_generate(kup, \"ku_csr\"))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with data from csr\", lcm.output\n        )\n\n    def test_135__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup csr_extensions\"\"\"\n        kup = 4\n        result = {\n            \"digital_signature\": False,\n            \"content_commitment\": False,\n            \"key_encipherment\": True,\n            \"data_encipherment\": False,\n            \"key_agreement\": False,\n            \"key_cert_sign\": False,\n            \"crl_sign\": False,\n            \"encipher_only\": False,\n            \"decipher_only\": False,\n        }\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(result, self.cahandler._kue_generate(kup, \"ku_csr\"))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with data from template\",\n            lcm.output,\n        )\n\n    def test_136__kue_generate(self):\n        \"\"\"CAhandler._kue_generate() - kup 0 csr_extensions\"\"\"\n        kup = 0\n        with self.assertLogs(\"test_a2c\", level=\"DEBUG\") as lcm:\n            self.assertEqual(\"ku_csr\", self.cahandler._kue_generate(kup, \"ku_csr\"))\n        self.assertIn(\n            \"DEBUG:test_a2c:Generate KeyUsage Extension with data from csr\", lcm.output\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_137___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic but not kuCritical\"\"\"\n        template_dic = {\"keyUse\": {\"foo\": \"bar\"}}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_138___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic kuCritical string\"\"\"\n        template_dic = {\"keyUse\": \"foo\", \"kuCritical\": \"1\"}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (True, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_139___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic kuCritical int\"\"\"\n        template_dic = {\"keyUse\": \"foo\", \"kuCritical\": 1}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (True, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_140___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic kuCritical string 0\"\"\"\n        template_dic = {\"keyUse\": \"foo\", \"kuCritical\": \"0\"}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_141___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic kuCritical string 0\"\"\"\n        template_dic = {\"keyUse\": \"foo\", \"kuCritical\": 0}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_142___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse in template_dic kuCritical triggers exception\"\"\"\n        template_dic = {\"keyUse\": \"foo\", \"kuCritical\": \"to fail\"}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_143___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - keyUse extension dic\"\"\"\n        template_dic = {}\n        csr_keyusage = Mock()\n        csr_keyusage.__str__ = Mock(return_value=\"foo\")\n        csr_extensions_dic = {\"keyUsage\": csr_keyusage}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._kue_generate\")\n    def test_144___keyusage_generate(self, mock_kuegen):\n        \"\"\"key usage generate - empty emplate dic and empty CSR dic\"\"\"\n        template_dic = {}\n        csr_extensions_dic = {}\n        mock_kuegen.return_value = \"kue_string\"\n        self.assertEqual(\n            (False, \"kue_string\"),\n            self.cahandler._keyusage_generate(template_dic, csr_extensions_dic),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_load\")\n    def test_145__enter__(self, mock_cfg):\n        \"\"\"test enter\"\"\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertTrue(mock_cfg.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_load\")\n    def test_146__enter__(self, mock_cfg):\n        \"\"\"test enter\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/est_proxy.xdb\"\n        mock_cfg.return_value = True\n        self.cahandler.__enter__()\n        self.assertFalse(mock_cfg.called)\n\n    def test_147_trigger(self):\n        \"\"\"test trigger\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None), self.cahandler.trigger(\"payload\")\n        )\n\n    def test_148_poll(self):\n        \"\"\"test poll\"\"\"\n        self.assertEqual(\n            (\"Method not implemented.\", None, None, \"poll_identifier\", False),\n            self.cahandler.poll(\"cert_name\", \"poll_identifier\", \"csr\"),\n        )\n\n    def test_149_stub_func(self):\n        \"\"\"test stubfunc\"\"\"\n        self.assertEqual(\"parameter\", self.cahandler._stub_func(\"parameter\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._item_insert\")\n    def test_150__store_cert(self, mock_i_insert, mock_c_insert):\n        \"\"\"test insert\"\"\"\n        mock_i_insert.return_value = 1\n        mock_c_insert.return_value = 2\n        self.cahandler._store_cert(\n            \"ca_id\", \"cert_name\", \"serial\", \"cert\", \"name_hash\", \"issuer_hash\"\n        )\n        self.assertTrue(mock_i_insert.called)\n        self.assertTrue(mock_c_insert.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.dict_from_row\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_151_revocation_search(\n        self, mock_open, mock_close, mock_dicfrow, mock_id_check\n    ):\n        \"\"\"revocation search\"\"\"\n        mock_id_check.return_value = True\n        mock_dicfrow.return_value = {\"foo\": \"bar\"}\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._revocation_search(\"column\", \"value\")\n        )\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n        self.assertTrue(mock_dicfrow.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.dict_from_row\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_152_revocation_search(\n        self, mock_open, mock_close, mock_dicfrow, mock_id_check\n    ):\n        \"\"\"revocation search  dicfromrow throws exception\"\"\"\n        mock_id_check.return_value = True\n        mock_dicfrow.side_effect = Exception(\"exc_dicfromrow\")\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.assertFalse(self.cahandler._revocation_search(\"column\", \"value\"))\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n        self.assertTrue(mock_dicfrow.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.dict_from_row\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_153_revocation_search(\n        self, mock_open, mock_close, mock_dicfrow, mock_id_check\n    ):\n        \"\"\"revocation search\"\"\"\n        mock_id_check.return_value = True\n        mock_dicfrow.return_value = {\"foo\": \"bar\"}\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.assertEqual(\n            {\"foo\": \"bar\"}, self.cahandler._revocation_search(\"column\", \"value\")\n        )\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n        self.assertTrue(mock_dicfrow.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.dict_from_row\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_154_revocation_search(\n        self, mock_open, mock_close, mock_dicfrow, mock_id_check\n    ):\n        \"\"\"revocation search  dicfromrow throws exception\"\"\"\n        mock_id_check.return_value = True\n        mock_dicfrow.side_effect = Exception(\"exc_dicfromrow\")\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.assertFalse(self.cahandler._revocation_search(\"column\", \"value\"))\n        self.assertTrue(mock_open.called)\n        self.assertTrue(mock_close.called)\n        self.assertTrue(mock_dicfrow.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._identifier_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.dict_from_row\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_155_revocation_search(\n        self, mock_open, mock_close, mock_dicfrow, mock_id_check\n    ):\n        \"\"\"revocation search  dicfromrow throws exception\"\"\"\n        mock_id_check.return_value = False\n        mock_dicfrow.side_effect = Exception(\"exc_dicfromrow\")\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(self.cahandler._revocation_search(\"column\", \"value\"))\n        self.assertIn(\n            \"WARNING:test_a2c:column: column not in revocations table\", lcm.output\n        )\n        self.assertFalse(mock_open.called)\n        self.assertFalse(mock_close.called)\n        self.assertFalse(mock_dicfrow.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_insert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._item_insert\")\n    def test_156__store_cert(self, mock_i_insert, mock_c_insert):\n        \"\"\"test insert\"\"\"\n        mock_i_insert.return_value = 1\n        mock_c_insert.return_value = 2\n        self.cahandler._store_cert(\n            \"ca_id\", \"cert_name\", \"serial\", \"cert\", \"name_hash\", \"issuer_hash\"\n        )\n        self.assertTrue(mock_i_insert.called)\n        self.assertTrue(mock_c_insert.called)\n\n    def test_157___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic and csr_extensions_dic are empty\"\"\"\n        template_dic = {}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (False, None),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_158___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template not critical\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\"}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (False, [\"eKeyUse\"]),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_159___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template critical string\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\", \"ekuCritical\": \"1\"}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (True, [\"eKeyUse\"]),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_160__extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template critical in\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\", \"ekuCritical\": 1}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (True, [\"eKeyUse\"]),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_161___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template critical zero\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\", \"ekuCritical\": \"0\"}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (False, [\"eKeyUse\"]),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_162___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template critical int zero\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\", \"ekuCritical\": 0}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (False, [\"eKeyUse\"]),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_163___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic eKeyUse in template convert to int fail\"\"\"\n        template_dic = {\"eKeyUse\": \"eKeyUse\", \"ekuCritical\": \"convertfail\"}\n        csr_extensions_dic = {}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                (False, [\"eKeyUse\"]),\n                self.cahandler._extended_keyusage_generate(\n                    template_dic, csr_extensions_dic\n                ),\n            )\n        self.assertIn(\n            \"ERROR:test_a2c:Failed to convert EKU critical flag to int, defaulting to False\",\n            lcm.output,\n        )\n\n    def test_164___extended_keyusage_generate(self):\n        \"\"\"_extended_keyusage_generate template dic unknown eKeyUse in template\"\"\"\n        template_dic = {\"eKeyUse\": \"unkeKeyUse\", \"ekuCritical\": \"1\"}\n        csr_extensions_dic = {}\n        self.assertEqual(\n            (True, []),\n            self.cahandler._extended_keyusage_generate(\n                template_dic, csr_extensions_dic\n            ),\n        )\n\n    def test_165__cdp_list_generate(self):\n        \"\"\"test _cdp_list_generate()\"\"\"\n        cdp_string = None\n        self.assertEqual([], self.cahandler._cdp_list_generate(cdp_string))\n\n    @patch(\"cryptography.x509.DistributionPoint\")\n    def test_166__cdp_list_generate(self, mock_cdp):\n        \"\"\"test _cdp_list_generate()\"\"\"\n        cdp_string = \"foo\"\n        mock_cdp.side_effect = [\"foo1\", \"foo2\"]\n        self.assertEqual([\"foo1\"], self.cahandler._cdp_list_generate(cdp_string))\n\n    @patch(\"cryptography.x509.DistributionPoint\")\n    def test_167__cdp_list_generate(self, mock_cdp):\n        \"\"\"test _cdp_list_generate()\"\"\"\n        cdp_string = \"foo, bar\"\n        mock_cdp.side_effect = [\"foo1\", \"foo2\"]\n        self.assertEqual(\n            [\"foo1\", \"foo2\"], self.cahandler._cdp_list_generate(cdp_string)\n        )\n\n    @patch(\"cryptography.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_modify\")\n    def test_168__cert_subject_generate(self, mock_submod, mock_name):\n        \"\"\"_cert_subject_generate()\"\"\"\n        req = Mock()\n        req.subject = \"subject\"\n        request_name = \"request_name\"\n        dn_dic = {}\n        self.assertEqual(\n            \"subject\", self.cahandler._cert_subject_generate(req, request_name, dn_dic)\n        )\n        self.assertFalse(mock_submod.called)\n        self.assertFalse(mock_name.called)\n\n    @patch(\"cryptography.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_modify\")\n    def test_169__cert_subject_generate(self, mock_submod, mock_name):\n        \"\"\"_cert_subject_generate()\"\"\"\n        req = Mock()\n        req.subject = None\n        mock_name.return_value = \"mock_name\"\n        request_name = \"request_name\"\n        dn_dic = {}\n        self.assertEqual(\n            \"mock_name\",\n            self.cahandler._cert_subject_generate(req, request_name, dn_dic),\n        )\n        self.assertFalse(mock_submod.called)\n        self.assertTrue(mock_name.called)\n\n    @patch(\"cryptography.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_modify\")\n    def test_170__cert_subject_generate(self, mock_submod, mock_name):\n        \"\"\"_cert_subject_generate()\"\"\"\n        req = Mock()\n        req.subject = None\n        mock_name.return_value = \"mock_name\"\n        mock_submod.return_value = \"mock_submod\"\n        request_name = \"request_name\"\n        dn_dic = {\"foo\": \"bar\"}\n        self.assertEqual(\n            \"mock_submod\",\n            self.cahandler._cert_subject_generate(req, request_name, dn_dic),\n        )\n        self.assertTrue(mock_submod.called)\n        self.assertTrue(mock_name.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    def test_171__extension_list_default(\n        self, mock_bc, mock_ku, mock_ski, mock_aki, mock_eku\n    ):\n        \"\"\"_extension_list_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_eku\"},\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._extension_list_default(cert, cert))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    def test_172__extension_list_default(\n        self, mock_bc, mock_ku, mock_ski, mock_aki, mock_eku\n    ):\n        \"\"\"_extension_list_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_eku\"},\n            {\"name\": \"mock_ski\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._extension_list_default(None, cert))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    def test_173__extension_list_default(\n        self, mock_bc, mock_ku, mock_ski, mock_aki, mock_eku\n    ):\n        \"\"\"_extension_list_default()\"\"\"\n        cert = Mock()\n        mock_bc.return_value = \"mock_bc\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        result = [\n            {\"name\": \"mock_bc\", \"critical\": True},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_eku\"},\n            {\"name\": \"mock_aki\", \"critical\": False},\n        ]\n        self.assertEqual(result, self.cahandler._extension_list_default(cert, None))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_default\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._xca_template_process\")\n    def test_174__extension_list_generate(\n        self, mock_template, mock_extlist, mock_convert\n    ):\n        \"\"\"_extension_list_generate()\"\"\"\n        cert = Mock()\n        mock_extlist.return_value = \"mock_extlist\"\n        mock_template.return_value = \"mock_template\"\n        mock_convert.return_value = \"mock_convert\"\n        csr_extensions_list = []\n        template_dic = {}\n        self.assertEqual(\n            \"mock_extlist\",\n            self.cahandler._extension_list_generate(\n                template_dic, cert, csr_extensions_list\n            ),\n        )\n        self.assertTrue(mock_extlist.called)\n        self.assertFalse(mock_template.called)\n        self.assertFalse(mock_convert.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_default\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._xca_template_process\")\n    def test_175__extension_list_generate(\n        self, mock_template, mock_extlist, mock_convert\n    ):\n        \"\"\"_extension_list_generate()\"\"\"\n        cert = Mock()\n        mock_extlist.return_value = \"mock_extlist\"\n        mock_template.return_value = \"mock_template\"\n        mock_convert.return_value = \"mock_convert\"\n        csr_extensions_list = []\n        template_dic = {\"foo\": \"bar\"}\n        self.assertEqual(\n            \"mock_template\",\n            self.cahandler._extension_list_generate(\n                template_dic, cert, csr_extensions_list\n            ),\n        )\n        self.assertFalse(mock_extlist.called)\n        self.assertTrue(mock_template.called)\n        self.assertFalse(mock_convert.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_default\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._xca_template_process\")\n    def test_176__extension_list_generate(\n        self, mock_template, mock_extlist, mock_convert\n    ):\n        \"\"\"_extension_list_generate()\"\"\"\n        cert = Mock()\n        mock_extlist.return_value = \"mock_extlist\"\n        mock_template.return_value = \"mock_template\"\n        mock_convert.return_value = \"mock_convert\"\n        ext = Mock()\n        csr_extensions_list = [ext]\n        template_dic = {}\n        self.assertEqual(\n            \"mock_extlist\",\n            self.cahandler._extension_list_generate(\n                template_dic, cert, cert, csr_extensions_list\n            ),\n        )\n        self.assertTrue(mock_extlist.called)\n        self.assertFalse(mock_template.called)\n        self.assertTrue(mock_convert.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectAlternativeName\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_default\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._xca_template_process\")\n    def test_177__extension_list_generate(\n        self, mock_template, mock_extlist, mock_convert, mock_san\n    ):\n        \"\"\"_extension_list_generate()\"\"\"\n        cert = Mock()\n        mock_extlist.return_value = [\"mock_extlist\"]\n        mock_template.return_value = \"mock_template\"\n        mock_convert.side_effect = [\"mock_convert\", \"subjectAltName\"]\n        mock_san.return_value = \"mock_san\"\n        ext = Mock()\n        csr_extensions_list = [ext, ext]\n        template_dic = {}\n        self.assertEqual(\n            [\"mock_extlist\", {\"name\": \"mock_san\", \"critical\": False}],\n            self.cahandler._extension_list_generate(\n                template_dic, cert, cert, csr_extensions_list\n            ),\n        )\n        self.assertTrue(mock_extlist.called)\n        self.assertFalse(mock_template.called)\n        self.assertTrue(mock_convert.called)\n\n    @patch(\"OpenSSL.crypto.X509.from_cryptography\")\n    def test_178__subject_name_hash_get(self, mock_x509):\n        \"\"\"_subject_name_hash_get()\"\"\"\n        # mock_x509 = Mock()\n        obj = Mock()\n        obj.subject_name_hash.return_value = 111111111111111111\n        mock_x509.return_value = obj\n        self.assertEqual(73429447, self.cahandler._subject_name_hash_get(\"cert\"))\n\n    @patch(\"OpenSSL.crypto.X509.from_cryptography\")\n    def test_179__subject_name_hash_get(self, mock_x509):\n        \"\"\"_subject_name_hash_get()\"\"\"\n        # mock_x509 = Mock()\n        obj = Mock()\n        obj.subject_name_hash.return_value = 11111111111111111\n        mock_x509.return_value = obj\n        self.assertEqual(651588039, self.cahandler._subject_name_hash_get(\"cert\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.NameAttribute\")\n    def test_180__subject_modify(self, mock_addr, mock_name):\n        \"\"\"_subject_modify()\"\"\"\n        mock_name.return_value = \"mock_name\"\n        mock_addr.return_value = \"mock_addr\"\n        dn_dic = {\"organizationalUnitName\": \"organizationalUnitName\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"mock_name\", self.cahandler._subject_modify(\"subject\", dn_dic)\n            )\n        self.assertIn(\"INFO:test_a2c:Rewrite OU to organizationalUnitName\", lcm.output)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.NameAttribute\")\n    def test_181__subject_modify(self, mock_addr, mock_name):\n        \"\"\"_subject_modify()\"\"\"\n        mock_name.return_value = \"mock_name\"\n        mock_addr.return_value = \"mock_addr\"\n        dn_dic = {\"organizationName\": \"organizationName\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"mock_name\", self.cahandler._subject_modify(\"subject\", dn_dic)\n            )\n        self.assertIn(\"INFO:test_a2c:Rewrite O to organizationName\", lcm.output)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.NameAttribute\")\n    def test_182__subject_modify(self, mock_addr, mock_name):\n        \"\"\"_subject_modify()\"\"\"\n        mock_name.return_value = \"mock_name\"\n        mock_addr.return_value = \"mock_addr\"\n        dn_dic = {\"localityName\": \"localityName\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"mock_name\", self.cahandler._subject_modify(\"subject\", dn_dic)\n            )\n        self.assertIn(\"INFO:test_a2c:Rewrite L to localityName\", lcm.output)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.NameAttribute\")\n    def test_183__subject_modify(self, mock_addr, mock_name):\n        \"\"\"_subject_modify()\"\"\"\n        mock_name.return_value = \"mock_name\"\n        mock_addr.return_value = \"mock_addr\"\n        dn_dic = {\"stateOrProvinceName\": \"stateOrProvinceName\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"mock_name\", self.cahandler._subject_modify(\"subject\", dn_dic)\n            )\n        self.assertIn(\"INFO:test_a2c:Rewrite ST to stateOrProvinceName\", lcm.output)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.Name\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.NameAttribute\")\n    def test_184__subject_modify(self, mock_addr, mock_name):\n        \"\"\"_subject_modify()\"\"\"\n        mock_name.return_value = \"mock_name\"\n        mock_addr.return_value = \"mock_addr\"\n        dn_dic = {\"countryName\": \"countryName\"}\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(\n                \"mock_name\", self.cahandler._subject_modify(\"subject\", dn_dic)\n            )\n        self.assertIn(\"INFO:test_a2c:Rewrite C to countryName\", lcm.output)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_185_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_chk.return_value = \"error\"\n        mock_prof.return_value = None\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertFalse(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_186_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_chk.return_value = \"error\"\n        mock_prof.return_value = None\n        self.assertEqual((\"error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertFalse(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_187_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = \"mock_db\"\n        mock_chk.return_value = None\n        mock_reqname.return_value = None\n        mock_prof.return_value = None\n        self.assertEqual((\"mock_db\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertFalse(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_188_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = None\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"request_name lookup failed\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_189_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = None\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"request_name lookup failed\", None, None, None),\n            self.cahandler.enroll(\"csr\"),\n        )\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_190_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = \"request_name\"\n        mock_ca.return_value = [None, \"cert\", \"id\"]\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"ca lookup failed\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertTrue(mock_csr.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_191_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = \"request_name\"\n        mock_ca.return_value = [\"key\", None, \"id\"]\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"ca lookup failed\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertTrue(mock_csr.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_192_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = \"request_name\"\n        mock_ca.return_value = [\"key\", \"cert\", None]\n        mock_prof.return_value = None\n        self.assertEqual(\n            (\"ca lookup failed\", None, None, None), self.cahandler.enroll(\"csr\")\n        )\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertTrue(mock_csr.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_193_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = \"request_name\"\n        mock_ca.return_value = [\"key\", \"cert\", \"caid\"]\n        mock_sign.return_value = [\"bundle\", \"raw\"]\n        mock_prof.return_value = None\n        self.assertEqual((None, \"bundle\", \"raw\", None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_chk.called)\n        self.assertTrue(mock_reqname.called)\n        self.assertTrue(mock_csr.called)\n        self.assertTrue(mock_b64.called)\n        self.assertTrue(mock_build.called)\n        self.assertTrue(mock_ca.called)\n        self.assertTrue(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.eab_profile_header_info_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cert_sign\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.build_pem_file\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_url_recode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._csr_import\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._requestname_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_194_enroll(\n        self,\n        mock_chk,\n        mock_reqname,\n        mock_csr,\n        mock_b64,\n        mock_build,\n        mock_ca,\n        mock_sign,\n        mock_prof,\n        mock_db,\n    ):\n        \"\"\"test enroll()\"\"\"\n        mock_db.return_value = None\n        mock_chk.return_value = None\n        mock_reqname.return_value = \"request_name\"\n        mock_ca.return_value = [\"key\", \"cert\", \"caid\"]\n        mock_sign.return_value = [\"bundle\", \"raw\"]\n        mock_prof.return_value = \"prof_error\"\n        self.assertEqual((\"prof_error\", None, None, None), self.cahandler.enroll(\"csr\"))\n        self.assertTrue(mock_chk.called)\n        self.assertFalse(mock_reqname.called)\n        self.assertFalse(mock_csr.called)\n        self.assertFalse(mock_b64.called)\n        self.assertFalse(mock_build.called)\n        self.assertFalse(mock_ca.called)\n        self.assertFalse(mock_sign.called)\n        self.assertTrue(mock_prof.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CertificateBuilder\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._store_cert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_name_hash_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._template_load\")\n    def test_195_cert_sign(\n        self,\n        mock_teml_load,\n        mock_str2byte,\n        mock_load,\n        mock_extlist,\n        mock_hash,\n        mock_store,\n        mock_chain,\n        mock_cvt,\n        mock_b64,\n        mock_builder,\n        mock_ecl,\n    ):\n        \"\"\"test cert sign\"\"\"\n        ca_cert = Mock()\n        ca_cert.subject = \"subject\"\n        mock_hash.return_value = \"mock_hash\"\n        mock_chain.return_value = \"mock_pem\"\n        mock_cvt.return_value = \"mock_cvt\"\n        # mock_builder.return_value.not_valid_before.return_value.not_valid_after.return_value.issuer_name.return_value.serial_number.return_value.public_key.return_value.subject_name.return_value.sign.return_value.public_bytes.return_value = 'mock_public_bytes'\n        mock_builder.return_value.not_valid_before.return_value.not_valid_after.return_value.issuer_name.return_value.serial_number.return_value.public_key.return_value.subject_name.return_value.sign.return_value.serial_number = (\n            1234\n        )\n        self.assertEqual(\n            (\"mock_pem\", \"mock_cvt\"),\n            self.cahandler._cert_sign(\n                \"csr\", \"request_name\", \"ca_key\", ca_cert, \"ca_id\"\n            ),\n        )\n        self.assertFalse(mock_teml_load.called)\n        self.assertTrue(mock_str2byte.called)\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_extlist.called)\n        self.assertTrue(mock_hash.called)\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_chain.called)\n        self.assertTrue(mock_cvt.called)\n        self.assertTrue(mock_builder.called)\n        self.assertFalse(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.enrollment_config_log\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CertificateBuilder\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._store_cert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_name_hash_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._template_load\")\n    def test_196_cert_sign(\n        self,\n        mock_teml_load,\n        mock_str2byte,\n        mock_load,\n        mock_extlist,\n        mock_hash,\n        mock_store,\n        mock_chain,\n        mock_cvt,\n        mock_b64,\n        mock_builder,\n        mock_ecl,\n    ):\n        \"\"\"test cert sign\"\"\"\n        ca_cert = Mock()\n        ca_cert.subject = \"subject\"\n        mock_hash.return_value = \"mock_hash\"\n        mock_chain.return_value = \"mock_pem\"\n        mock_cvt.return_value = \"mock_cvt\"\n        self.cahandler.template_name = \"template_name\"\n        mock_extlist.return_value = [{\"name\": \"name\", \"critical\": True}]\n        mock_teml_load.return_value = [{\"foo\": \"bar\"}, {\"foo\": \"bar\"}]\n        self.cahandler.enrollment_config_log = True\n        mock_builder.return_value.not_valid_before.return_value.not_valid_after.return_value.issuer_name.return_value.serial_number.return_value.public_key.return_value.add_extension.return_value.subject_name.return_value.sign.return_value.serial_number = (\n            1234\n        )\n        self.assertEqual(\n            (\"mock_pem\", \"mock_cvt\"),\n            self.cahandler._cert_sign(\n                \"csr\", \"request_name\", \"ca_key\", ca_cert, \"ca_id\"\n            ),\n        )\n        self.assertTrue(mock_teml_load.called)\n        self.assertTrue(mock_str2byte.called)\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_extlist.called)\n        self.assertTrue(mock_hash.called)\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_chain.called)\n        self.assertTrue(mock_cvt.called)\n        self.assertTrue(mock_builder.called)\n        self.assertTrue(mock_ecl.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CertificateBuilder\")\n    @patch(\"examples.ca_handler.xca_ca_handler.b64_encode\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_byte_to_string\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._pemcertchain_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._store_cert\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._subject_name_hash_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extension_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.load_pem_x509_csr\")\n    @patch(\"examples.ca_handler.xca_ca_handler.convert_string_to_byte\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._template_load\")\n    def test_197_cert_sign(\n        self,\n        mock_teml_load,\n        mock_str2byte,\n        mock_load,\n        mock_extlist,\n        mock_hash,\n        mock_store,\n        mock_chain,\n        mock_cvt,\n        mock_b64,\n        mock_builder,\n    ):\n        \"\"\"test cert sign\"\"\"\n        ca_cert = Mock()\n        ca_cert.subject = \"subject\"\n        mock_hash.return_value = \"mock_hash\"\n        mock_chain.return_value = \"mock_pem\"\n        mock_cvt.return_value = \"mock_cvt\"\n        self.cahandler.template_name = \"template_name\"\n        mock_extlist.return_value = [{\"name\": \"name\", \"critical\": True}]\n        mock_teml_load.return_value = [{\"foo\": \"bar\"}, {\"validity\": 30}]\n        mock_builder.return_value.not_valid_before.return_value.not_valid_after.return_value.issuer_name.return_value.serial_number.return_value.public_key.return_value.add_extension.return_value.subject_name.return_value.sign.return_value.serial_number = (\n            1234\n        )\n        self.assertEqual(\n            (\"mock_pem\", \"mock_cvt\"),\n            self.cahandler._cert_sign(\n                \"csr\", \"request_name\", \"ca_key\", ca_cert, \"ca_id\"\n            ),\n        )\n        self.assertTrue(mock_teml_load.called)\n        self.assertTrue(mock_str2byte.called)\n        self.assertTrue(mock_load.called)\n        self.assertTrue(mock_extlist.called)\n        self.assertTrue(mock_hash.called)\n        self.assertTrue(mock_store.called)\n        self.assertTrue(mock_chain.called)\n        self.assertTrue(mock_cvt.called)\n        self.assertTrue(mock_builder.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_198_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"foo\": \"bar\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertFalse(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_199_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"foo\": \"bar\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_ekug.return_value = (False, [\"eku_list\"])\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_eku\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertTrue(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertFalse(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_200_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"crlDist\": \"crlDist\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_crl.return_value = \"mock_crl\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_crl\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertTrue(mock_crl.called)\n        self.assertFalse(mock_bc.called)\n        self.assertTrue(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_201_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"ca\": \"1\", \"bcCritical\": True}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": True, \"name\": \"mock_bc\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_202_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"ca\": \"1\", \"bcCritical\": False}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_bc\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_203_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"ca\": \"1\", \"bcCritical\": \"aa\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_bc\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_204_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"ca\": \"2\", \"bcCritical\": \"aa\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_bc\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._extended_keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._cdp_list_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._keyusage_generate\")\n    @patch(\"examples.ca_handler.xca_ca_handler.BasicConstraints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.x509.CRLDistributionPoints\")\n    @patch(\"examples.ca_handler.xca_ca_handler.ExtendedKeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.KeyUsage\")\n    @patch(\"examples.ca_handler.xca_ca_handler.AuthorityKeyIdentifier\")\n    @patch(\"examples.ca_handler.xca_ca_handler.SubjectKeyIdentifier\")\n    def test_205_xca_template_process(\n        self,\n        mock_ski,\n        mock_aki,\n        mock_ku,\n        mock_eku,\n        mock_crl,\n        mock_bc,\n        mock_kug,\n        mock_cdp,\n        mock_ekug,\n    ):\n        \"\"\"test _xca_template_process()\"\"\"\n        csr_extensions_dic = {}\n        template_dic = {\"ca\": \"1\"}\n        cert = Mock()\n        cert.public_key.return_value = \"public_key\"\n        mock_ski.from_public_key.return_value = \"mock_ski\"\n        mock_aki.from_issuer_public_key.return_value = \"mock_aki\"\n        mock_ku.return_value = \"mock_ku\"\n        mock_eku.return_value = \"mock_eku\"\n        mock_bc.return_value = \"mock_bc\"\n        mock_ekug.return_value = (False, {})\n        mock_kug.return_value = (True, {\"mock_kug\": \"mock_kug\"})\n        mock_cdp.return_value = [\"mock_cdp\"]\n        result = [\n            {\"name\": \"mock_ski\", \"critical\": False},\n            {\"name\": \"mock_aki\", \"critical\": False},\n            {\"name\": \"mock_ku\", \"critical\": True},\n            {\"critical\": False, \"name\": \"mock_bc\"},\n        ]\n        self.assertEqual(\n            result,\n            self.cahandler._xca_template_process(\n                template_dic, csr_extensions_dic, cert, cert\n            ),\n        )\n        self.assertTrue(mock_ku.called)\n        self.assertFalse(mock_eku.called)\n        self.assertTrue(mock_ekug.called)\n        self.assertTrue(mock_kug.called)\n        self.assertFalse(mock_crl.called)\n        self.assertTrue(mock_bc.called)\n        self.assertFalse(mock_cdp.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_206_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        mock_oct.return_value = \"660\"\n        mock_access.side_effect = [True, True]\n        mock_load.return_value = \"ca_key\"\n        self.assertEqual(None, self.cahandler._db_check())\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_207_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        mock_oct.return_value = \"660\"\n        mock_access.side_effect = [False, True]\n        mock_load.return_value = \"ca_key\"\n        self.assertEqual(\n            \"xdb_file xdb_file is not readable\", self.cahandler._db_check()\n        )\n        self.assertFalse(mock_load.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_208_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        mock_oct.return_value = \"660\"\n        mock_access.side_effect = [True, False]\n        mock_load.return_value = \"ca_key\"\n        self.assertEqual(\n            \"xdb_file xdb_file is not writeable\", self.cahandler._db_check()\n        )\n        self.assertFalse(mock_load.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_209_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        mock_oct.return_value = \"660\"\n        mock_access.side_effect = [True, True]\n        mock_load.return_value = None\n        self.assertEqual(\n            \"ca_key_load failed. PLease check passphrase\", self.cahandler._db_check()\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_210_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        self.cahandler.xdb_permission = \"220\"\n        mock_oct.return_value = \"660\"\n        mock_access.side_effect = [True, True]\n        mock_load.return_value = \"ca_key\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._db_check())\n        self.assertIn(\n            \"WARNING:test_a2c:File permissions 660 for 'xdb_file' are too permissive. Should be 220.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_access.called)\n        self.assertTrue(mock_load.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_211_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        self.cahandler.xdb_permission = \"220\"\n        mock_oct.return_value = \"260\"\n        mock_access.side_effect = [True, True]\n        mock_load.return_value = \"ca_key\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._db_check())\n        self.assertIn(\n            \"WARNING:test_a2c:File permissions 260 for 'xdb_file' are too permissive. Should be 220.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_access.called)\n        self.assertTrue(mock_load.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._ca_key_load\")\n    @patch(\"examples.ca_handler.xca_ca_handler.oct\")\n    @patch(\"os.access\")\n    @patch(\"os.stat\")\n    def test_212_db_check(self, mock_stat, mock_access, mock_oct, mock_load):\n        \"\"\"test _db_check()\"\"\"\n        self.cahandler.xdb_file = \"xdb_file\"\n        mock_stat.return_value.st_mode = 2222\n        self.cahandler.xdb_permission = \"220\"\n        mock_oct.return_value = \"222\"\n        mock_access.side_effect = [True, True]\n        mock_load.return_value = \"ca_key\"\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertEqual(None, self.cahandler._db_check())\n        self.assertIn(\n            \"WARNING:test_a2c:File permissions 222 for 'xdb_file' are too permissive. Should be 220.\",\n            lcm.output,\n        )\n        self.assertTrue(mock_access.called)\n        self.assertTrue(mock_load.called)\n\n    def test_213_table_check(self):\n        \"\"\"test _table_check() method\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertTrue(self.cahandler._table_check(\"requests\"))\n        self.assertTrue(self.cahandler._table_check(\"view_certs\"))\n        self.assertFalse(self.cahandler._table_check(\"unknown_table\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._columnnames_get\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._table_check\")\n    def test_214_identifier_check(self, mock_chk, mock_col):\n        \"\"\"test _identifier_check() method\"\"\"\n        mock_chk.return_value = True\n        mock_col.return_value = [\"item\", \"foo\"]\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        self.assertTrue(self.cahandler._identifier_check(\"certs\", \"item\"))\n        self.assertFalse(self.cahandler._identifier_check(\"certs\", \"unkown\"))\n        self.assertTrue(self.cahandler._identifier_check(\"certs\", \"certs.foo\"))\n        self.assertFalse(self.cahandler._identifier_check(\"certs\", \"certs.unkown\"))\n        self.assertTrue(self.cahandler._identifier_check(\"certs\", \"certs__foo\"))\n        self.assertFalse(self.cahandler._identifier_check(\"certs\", \"certs__unkown\"))\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._table_check\")\n    def test_215_identifier_check(self, mock_tg):\n        \"\"\"test _identifier_check() method\"\"\"\n        mock_tg.return_value = False\n        with self.assertLogs(\"test_a2c\", level=\"INFO\") as lcm:\n            self.assertFalse(\n                self.cahandler._identifier_check(\"unknown_table\", \"unkown\")\n            )\n        self.assertIn(\n            \"WARNING:test_a2c:Table 'unknown_table' does not exist in the database.\",\n            lcm.output,\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_close\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_open\")\n    def test_216_columnnames_get(self, mock_open, mock_close):\n        \"\"\"test _columnnames_get() method\"\"\"\n        self.cahandler.xdb_file = self.dir_path + \"/ca/acme2certifier.xdb\"\n        mock_open.return_value = True\n        mock_close.return_value = True\n        self.cahandler.cursor = Mock()\n        self.cahandler.cursor.description = [[\"foo\", \"foobar\"], [\"foo1\", \"bar1\"]]\n        self.assertEqual(\n            [\"foo\", \"foo1\"],\n            self.cahandler._columnnames_get(\"requests\"),\n        )\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_217_handler_check(self, mock_cfg, mock_db):\n        \"\"\"test handler_check() method\"\"\"\n        mock_cfg.return_value = False\n        mock_db.return_value = False\n        self.assertFalse(self.cahandler.handler_check())\n        self.assertTrue(mock_cfg.called)\n        self.assertTrue(mock_db.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_218_handler_check(self, mock_cfg, mock_db):\n        \"\"\"test handler_check() method\"\"\"\n        mock_cfg.return_value = False\n        mock_db.return_value = \"db_error\"\n        self.assertEqual(\"db_error\", self.cahandler.handler_check())\n        self.assertTrue(mock_cfg.called)\n        self.assertTrue(mock_db.called)\n\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._db_check\")\n    @patch(\"examples.ca_handler.xca_ca_handler.CAhandler._config_check\")\n    def test_219_handler_check(self, mock_cfg, mock_db):\n        \"\"\"test handler_check() method\"\"\"\n        mock_cfg.return_value = \"cfg_error\"\n        mock_db.return_value = \"db_error\"\n        self.assertEqual(\"cfg_error\", self.cahandler.handler_check())\n        self.assertTrue(mock_cfg.called)\n        self.assertFalse(mock_db.called)\n\n\nif __name__ == \"__main__\":\n\n    unittest.main()\n"
  },
  {
    "path": "tools/a2c_cli.py",
    "content": "#!/usr/bin/python3\n# -*- coding: utf-8 -*-\n\"\"\"acme2certifier cli client\"\"\"\nimport logging\nimport datetime\nimport re\nimport argparse\nimport os.path\nimport sys\nimport time\nimport random\nfrom string import digits, ascii_letters\nimport json\nimport csv\nfrom jwcrypto import jwk, jws\nfrom jwcrypto.common import json_encode\nimport requests\n\nVERSION = \"0.0.1\"\n\nCLI_INTRO = \"\"\"acme2certifier command-line interface\n\nCopyright (c) 2022 GrindSa\n\nThis software is provided free of charge. Copying and redistribution is\nencouraged.\n\nIf you appreciate this software and you would like to support future\ndevelopment please consider donating to me.\n\nType /help for available commands\n\"\"\"\n\n\ndef csv_dump(logger, filename, content):\n    \"\"\"dump content csv file\"\"\"\n    logger.debug(\"csv_dump(%s)\", filename)\n    with open(filename, \"w\", newline=\"\", encoding=\"utf-8\") as file_:\n        writer = csv.writer(\n            file_, delimiter=\",\", quotechar='\"', quoting=csv.QUOTE_NONNUMERIC\n        )\n        writer.writerows(content)\n\n\ndef generate_random_string(logger, length):\n    \"\"\"generate random string to be used as name\"\"\"\n    logger.debug(\"generate_random_string()\")\n    char_set = digits + ascii_letters\n    return \"\".join(random.choice(char_set) for _ in range(length))\n\n\ndef file_dump(logger, filename, data_):\n    \"\"\"dump content to  file\"\"\"\n    logger.debug(\"file_dump(%s)\", filename)\n    with open(filename, \"w\", encoding=\"utf8\") as file_:\n        file_.write(data_)  # lgtm [py/clear-text-storage-sensitive-data]\n\n\ndef file_load(logger, filename):\n    \"\"\"load file at once\"\"\"\n    logger.debug(\"file_open(%s)\", filename)\n    with open(filename, encoding=\"utf8\") as _file:\n        lines = _file.read()\n    return lines\n\n\ndef is_url(string):\n    \"\"\"check if sting is a valid url\"\"\"\n    regex = re.compile(\n        r\"^(?:http|ftp)s?://\"  # http:// or https://\n        r\"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+(?:[A-Z]{2,6}\\.?|[A-Z0-9-]{2,}\\.?)|\"  # domain...\n        r\"localhost|\"  # localhost...\n        r\"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\"  # ...or ip\n        r\"(?::\\d+)?\"  # optional port\n        r\"(?:/?|[/?]\\S+)$\",\n        re.IGNORECASE,\n    )\n\n    return re.match(regex, string)\n\n\ndef logger_setup(debug):\n    \"\"\"setup logger\"\"\"\n    if debug:\n        log_mode = logging.DEBUG\n    else:\n        log_mode = logging.INFO\n\n    log_format = \"%(asctime)s - a2c_cli - %(levelname)s - %(message)s\"\n\n    logging.basicConfig(format=log_format, datefmt=\"%Y-%m-%d %H:%M:%S\", level=log_mode)\n    logger = logging.getLogger(\"a2c_cli\")\n    return logger\n\n\nclass KeyOperations(object):\n    \"\"\"key operations class\"\"\"\n\n    def __init__(self, logger=None, printcommand=None):\n        # CLIParser(self)\n        self.logger = logger\n        self.print = printcommand\n\n    def generate(self, filename):\n        \"\"\"generate and store key\"\"\"\n        self.logger.debug(\"KeyOperations.generate(%s)\", filename)\n        self.print(\"generating keys...\", printreturn=False)\n        key = jwk.JWK.generate(\n            kty=\"RSA\",\n            size=2048,\n            alg=\"RSA-OAEP-256\",\n            use=\"sig\",\n            kid=generate_random_string(self.logger, 12),\n        )\n        public_key = key.export_public(as_dict=True)\n        private_key = key.export_private(as_dict=True)\n\n        try:\n            file_dump(\n                self.logger,\n                f\"{filename}.pub\",\n                json.dumps(public_key, indent=4, sort_keys=True),\n            )\n            file_dump(\n                self.logger,\n                f\"{filename}.private\",\n                json.dumps(private_key, indent=4, sort_keys=True),\n            )\n            self.print(\"done...\", printreturn=False)\n            self.print(\n                f\"Keep the private key {filename}.pub for yourself\", printreturn=False\n            )\n            self.print(\n                f\"Give the public key {filename}.pub to your acme2certifier administrator\"\n            )\n        except Exception as err_:\n            self.logger.error(\"Key generation failed: %s\", err_)\n            self.print(\"Key generation failed with error: %s\", err_)\n        return key\n\n    def load(self, filename):\n        \"\"\"load existing key\"\"\"\n        self.logger.debug(\"KeyOperations.load(%s)\", filename)\n        if os.path.exists(filename):\n            self.print(f\"loading {filename}\", printreturn=False)\n            content = file_load(self.logger, filename)\n            key = jwk.JWK.from_json(content)\n            self.print(\"done...\", printreturn=False)\n        else:\n            self.print(f\"Could not find {filename}\")\n            key = None\n        return key\n\n\nclass MessageOperations(object):\n    \"\"\"message operations class\"\"\"\n\n    def __init__(self, logger=None, printcommand=None):\n        # CLIParser(self)\n        self.logger = logger\n        self.print = printcommand\n\n    def sign(self, key, data, cli_type=\"Unknown\"):\n        \"\"\"sign message\"\"\"\n        self.logger.debug(\"MessageOperations.sign()\")\n        protected = {\"typ\": \"JOSE+JSON\", \"kid\": key[\"kid\"], \"alg\": \"RS256\"}\n        plaintext = {\"data\": data, \"type\": cli_type, \"exp\": int(time.time()) + (5 * 60)}\n        mjws = jws.JWS(payload=json_encode(plaintext))\n        mjws.add_signature(key, None, json_encode(protected))\n        return mjws.serialize()\n\n    def send(self, server=None, message=None):\n        \"\"\"send message\"\"\"\n        self.logger.debug(f\"MessageOperations.send({server})\")\n        req = requests.post(f\"{server}/housekeeping\", data=message, timeout=20)\n        return req\n\n\nclass CommandLineInterface(object):\n    \"\"\"cli class\"\"\"\n\n    def __init__(self):\n        # CLIParser(self)\n        parser = argparse.ArgumentParser()\n\n        parser.add_argument(\n            \"-d\",\n            \"--debug\",\n            action=\"store_true\",\n            help=\"Show debug messages\",\n            dest=\"debug\",\n        )\n\n        parser.add_argument(\n            \"-b\",\n            \"--batchfile\",\n            action=\"store\",\n            help=\"batch file to execute\",\n            dest=\"batchfile\",\n        )\n        results = parser.parse_args()\n\n        self.logger = logger_setup(results.debug)\n        self.status = \"server missing\"\n        self.server = None\n        self.key = None\n\n        if results.batchfile:\n            self._load_cfg(results.batchfile)\n\n    def _load_cfg(self, ifile):\n        \"\"\"load config\"\"\"\n        self.logger.debug(\"CommandLineInterface._load_cfg()\")\n\n        with open(ifile, \"r\", encoding=\"utf8\") as fha:\n            for lin in fha:\n                line = lin.rstrip()\n                if line.startswith(\"sleep\"):\n                    try:\n                        (_sleep, tme) = line.split(\" \", 1)\n                        time.sleep(int(tme))\n                    except Exception:\n                        time.sleep(1)\n                else:\n                    if line.startswith(\"#\") is False:\n                        self._command_check(line)\n\n    def _cli_print(self, text, date_print=True, printreturn=True):\n        \"\"\"cli printout text\"\"\"\n        self.logger.debug(\"CommandLineInterface._cli_print()\")\n        if text:\n            if date_print:\n                now = datetime.datetime.now().strftime(\"%H:%M:%S\")\n                if printreturn:\n                    print(f\"{now} {text}\\n\")\n                else:\n                    print(f\"{now} {text}\")\n            else:\n                print(text)\n\n    def _command_check(self, command):\n        \"\"\"check command\"\"\"\n        # pylint: disable=c0325\n        self.logger.debug(\"CommandLineInterface._commend_check(): %s\", command)\n        if command in (\"help\", \"H\"):\n            self.help_print()\n        elif command.startswith(\"server\"):\n            self._server_set(command)\n        elif command.startswith(\"key\"):\n            self._key_operations(command)\n        elif command.startswith(\"config\"):\n            self._config_operations(command)\n        elif command in (\"quit\", \"Q\"):\n            self._quit()\n        elif self.status == \"Configured\":\n            if command.startswith(\"message\"):\n                self._message_operations(command)\n            elif command.startswith(\"report\"):\n                self._report_operations(command)\n            elif command.startswith(\"certificate\"):\n                self._certificate_operations(command)\n            else:\n                if command:\n                    self._cli_print(f'unknown command: \"/{command}\"')\n                    self.help_print()\n        else:\n            self._cli_print(f'Unknown command: \"{command}\"')\n            self.help_print()\n\n    def _certificate_operations(self, command):\n        self.logger.debug(\"CommandLineInterface._certificate_operations(): %s\", command)\n\n    def _config_operations(self, command):\n        self.logger.debug(\"CommandLineInterface._config_operations(): %s\", command)\n        self._cli_print(f\"server: {self.server}\", printreturn=False)\n        self._cli_print(f\"key: {self.key}\", printreturn=False)\n        self._cli_print(f\"status: {self.status}\", printreturn=False)\n\n    def _exec_cmd(self, cmdinput):\n        \"\"\"execute command\"\"\"\n        self.logger.debug(\"CommandLineInterface._exec_cmd(): %s\", cmdinput)\n        cmdinput = cmdinput.rstrip()\n        # skip empty commands\n        if len(cmdinput) <= 1:\n            return\n\n        if cmdinput.startswith(\"/\"):\n            cmdinput = cmdinput[1:]\n        else:\n            self._cli_print(\"Please enter a valid command!\")\n            self.help_print()\n            return\n\n        self._command_check(cmdinput)\n\n    def _intro_print(self):\n        \"\"\"print cli intro\"\"\"\n        self.logger.debug(\"CommandLineInterface._intro_print()\")\n        self._cli_print(CLI_INTRO.format(cliversion=VERSION))\n\n    def _key_operations(self, command):\n        \"\"\"key operations\"\"\"\n        self.logger.debug(\"CommandLineInterface._key_operations(%s)\", command)\n\n        try:\n            (_key, command, argument) = command.split(\" \", 2)\n        except Exception:\n            self._cli_print(f'incomplete key-operations command: \"{command}\"')\n            _key = None  # lgtm [py/unused-local-variable]\n            command = None\n            argument = None  # lgtm [py/unused-local-variable]\n\n        if command and argument:\n            key = KeyOperations(self.logger, self._cli_print)\n            if command == \"generate\":\n                self.key = key.generate(argument)\n            elif command == \"load\":\n                self.key = key.load(argument)\n            else:\n                self._cli_print(f'unknown key command: \"{command}\"')\n\n            if self.server:\n                self.status = \"Configured\"\n\n    def _message_operations(self, command):\n        \"\"\"message operations\"\"\"\n        self.logger.debug(\"CommandLineInterface._message_operations()\")\n\n        try:\n            (_key, command, argument) = command.split(\" \", 2)\n        except Exception:\n            self._cli_print(f'incomplete message-operations command: \"{command}\"')\n            _key = None  # lgtm [py/unused-local-variable]\n            command = None\n            argument = None  # lgtm [py/unused-local-variable]\n\n        if command and argument:\n            message = MessageOperations(self.logger, self._cli_print)\n\n            if command == \"sign\":\n                signed_message = message.sign(key=self.key, data=argument)\n                self._cli_print(signed_message)\n            elif command.startswith(\"send\"):\n                signed_message = message.sign(key=self.key, data=argument)\n                message.send(server=self.server, message=signed_message)\n\n    def _report_operations(self, command):\n        \"\"\"report operations\"\"\"\n        self.logger.debug(\"CommandLineInterface._message_operations()\")\n        try:\n            (_key, command, filename) = command.split(\" \", 2)\n        except Exception:\n            self._cli_print(f'incomplete report-operations command: \"{command}\"')\n            command = None\n            filename = None  # lgtm [py/unused-local-variable]\n\n        if command and filename:\n            try:\n                (_filename, format_) = filename.lower().split(\".\", 2)\n            except Exception:\n                self._cli_print(f'incomplete filename: \"{command}\"')\n                format_ = None\n\n            if format_ in (\"csv\", \"json\"):\n                self._report_generate(filename, format_, command)\n            else:\n                self._cli_print(\n                    f'Unknown report format \"{format_}\". Must be either \"csv\" or \"json\"'\n                )\n\n    def _report_generate(self, filename, format_, command):\n        \"\"\"generate report\"\"\"\n        self.logger.debug(\"CommandLineInterface._report_generate()\")\n\n        # process report request\n        message = MessageOperations(self.logger, self._cli_print)\n        signed_message = message.sign(\n            key=self.key, cli_type=\"report\", data={\"name\": command, \"format\": format_}\n        )\n        response = message.send(server=self.server, message=signed_message)\n        if response.status_code == 200:\n            if format_ == \"csv\":\n                csv_dump(self.logger, filename, response.json())\n            else:\n                file_dump(\n                    self.logger,\n                    filename,\n                    json.dumps(response.json(), indent=4, sort_keys=True),\n                )\n            self._cli_print(f\"saving report to {filename}\")\n        else:\n            if \"message\" in response.json():\n                message = response.json()[\"message\"]\n            elif \"detail\" in response.json():\n                message = response.json()[\"detail\"]\n            else:\n                message = None\n            self._cli_print(f\"ERROR: {response.status_code} - {message}\")\n\n    def _prompt_get(self):\n        \"\"\"get prompt\"\"\"\n        self.logger.debug(\"CommandLineInterface._prompt_get()\")\n        return f\"[{self.status}]:\"\n\n    def _quit(self):\n        \"\"\"quit (whatever)\"\"\"\n        self.logger.debug(\"CommandLineInterface.quit()\")\n        sys.exit(0)\n\n    def _server_set(self, server):\n        \"\"\"configure server\"\"\"\n        self.logger.debug(\"CommandLineInterface._server_set(%s)\", server)\n\n        (_command, url) = server.split(\" \")\n        if is_url(url):\n            self.server = url\n            if self.key:\n                self.status = \"Configured\"\n            else:\n                self.status = \"Key missing\"\n        else:\n            self._cli_print(f\"{url} is not a valid url\")\n\n    def help_print(self):\n        \"\"\"help screen\"\"\"\n        self.logger.debug(\"CommandLineInterface.help_print()\")\n        helper = \"\"\"-------------------------------------------------------------------------------\n/certificate search <parameter> <string> - search certificate for a certain parameter\n/certificate revoke <identifier> - revoke certificate on given uuid\n/report certificates <filename> - download certificate report in either csf or json format\n/report accounts <filename> - download certificate report in either csf or json format\n/config show - show configuration\n/key generate <filename> - generate a new JWK pair\n/key load <filename> - load exisitng private JWK from file\n/quit /Q - quit\n\n\"\"\"\n        self._cli_print(helper, date_print=False)\n\n    def start(self):\n        \"\"\"start\"\"\"\n        self.logger.debug(\"CommandLineInterface.start()\")\n        self._intro_print()\n\n        while True:\n            cmd = input(self._prompt_get()).strip()\n            self._exec_cmd(cmd)\n\n\nif __name__ == \"__main__\":\n\n    # start cli\n    CLI = CommandLineInterface()  # pragma: no cover\n    CLI.start()  # pragma: no cover\n"
  },
  {
    "path": "tools/cert_poll.py",
    "content": "#!/usr/bin/python\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\nimport os.path\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\nfrom acme_srv.db_handler import initialize  # nopep8\n\ninitialize()\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom acme_srv.certificate import Certificate  # nopep8\n\nif __name__ == \"__main__\":\n\n    DEBUG = True\n\n    # timeout between the different polling request\n    TIMEOUT = 1\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    with Certificate(DEBUG, \"foo\", LOGGER) as certificate:\n        # search certificates in status \"processing\"\n        CERT_LIST = certificate.certlist_search(\n            \"order__status_id\", 4, (\"name\", \"poll_identifier\", \"csr\", \"order__name\")\n        )\n\n        for cert in CERT_LIST:\n            # check status of certificate\n            certificate.poll(\n                cert[\"name\"], cert[\"poll_identifier\"], cert[\"csr\"], cert[\"order__name\"]\n            )\n            # time.sleep(TIMEOUT)\n"
  },
  {
    "path": "tools/cliuser_mgmt.py",
    "content": "#!/usr/bin/python\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\nimport json\nimport argparse\nimport os.path\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom acme_srv.housekeeping import Housekeeping  # nopep8\n\n\ndef arg_parse():\n    \"\"\"simple argparser\"\"\"\n    parser = argparse.ArgumentParser(\n        description=\"match_import.py - update matches in database\"\n    )\n    parser.add_argument(\n        \"-d\", \"--debug\", help=\"debug mode\", action=\"store_true\", default=False\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--certificateadmin\",\n        help=\"grant permissions to manage certificates\",\n        action=\"store_true\",\n        default=False,\n    )\n    parser.add_argument(\n        \"-r\",\n        \"--reportadmin\",\n        help=\"grant permissions to download reports\",\n        action=\"store_true\",\n        default=False,\n    )\n    parser.add_argument(\n        \"-u\",\n        \"--useradmin\",\n        help=\"grant permissions to manage cli users\",\n        action=\"store_true\",\n        default=False,\n    )\n    parser.add_argument(\"-e\", \"--email\", help=\"email address\", default=None)\n    parser.add_argument(\"-k\", \"--keyfile\", help=\"file containing JWK\")\n    parser.add_argument(\"-n\", \"--jwkname\", help=\"name of the key\")\n    clist = parser.add_mutually_exclusive_group()\n    clist.add_argument(\"--list\", help=\"list users\", action=\"store_true\", default=False)\n    clist.add_argument(\n        \"--delete\", help=\"delete user\", action=\"store_true\", default=False\n    )\n\n    args = parser.parse_args()\n\n    debug = args.debug\n    config_dic = {\n        \"debug\": args.debug,\n        \"permissions\": {\n            \"certificateadmin\": args.certificateadmin,\n            \"reportadmin\": args.reportadmin,\n            \"cliadmin\": args.useradmin,\n        },\n    }\n\n    if args.jwkname:\n        config_dic[\"jwkname\"] = args.jwkname\n    if args.delete:\n        config_dic[\"delete\"] = args.delete\n    if args.list:\n        config_dic[\"list\"] = args.list\n    if args.email:\n        config_dic[\"email\"] = args.email\n    if args.keyfile:\n        if os.path.exists(args.keyfile):\n            config_dic[\"jwk\"] = json.loads(file_load(args.keyfile))\n        else:\n            print(f'Error: keyfile \"{args.keyfile}\" does not exist')\n\n    return (debug, config_dic)\n\n\ndef file_load(filename):\n    \"\"\"load file at once\"\"\"\n    with open(filename, encoding=\"utf8\") as _file:\n        lines = _file.read()\n    return lines\n\n\nif __name__ == \"__main__\":\n\n    (DEBUG, CONFIG_DIC) = arg_parse()\n\n    # the cli program needs ot be chatty\n    CONFIG_DIC[\"silent\"] = False\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    with Housekeeping(DEBUG, LOGGER) as housekeeping:\n        # cli usermgr\n        result = housekeeping.cli_usermgr(CONFIG_DIC)\n"
  },
  {
    "path": "tools/db_update.py",
    "content": "#!/usr/bin/python\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\n\nsys.path.insert(0, \"..\")\nsys.path.insert(1, \".\")\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom acme_srv.db_handler import DBstore  # nopep8\n\nif __name__ == \"__main__\":\n\n    DEBUG = True\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    # connect to database and do the upgrade\n    DBSTORE = DBstore(DEBUG, LOGGER)\n    DBSTORE.db_update()\n"
  },
  {
    "path": "tools/django_secret_keygen.py",
    "content": "#!/usr/bin/python3\n\"\"\"secret key generator for django project\"\"\"\n# pylint: disable=E0401\nfrom django.core.management.utils import get_random_secret_key\n\nprint(get_random_secret_key())  # lgtm [py/clear-text-logging-sensitive-data]\n"
  },
  {
    "path": "tools/django_update.py",
    "content": "#!/usr/bin/python3\n\"\"\"database updater\"\"\"\n# pylint: disable=C0209, E0401, C0413\nimport sys\nimport os\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"acme2certifier.settings\")\n\n# Global variables to store imported modules (for testing)\ndjango = None\ncall_command = None\nStatus = None\nHousekeeping = None\n__dbversion__ = None\n\nSTATUS_LIST = [\n    \"invalid\",\n    \"pending\",\n    \"ready\",\n    \"processing\",\n    \"valid\",\n    \"expired\",\n    \"deactivated\",\n    \"revoked\",\n]\n\n\ndef setup_django():\n    \"\"\"Setup Django and import required modules\"\"\"\n    global django, call_command, Status, Housekeeping, __dbversion__\n\n    try:\n        import django as django_module  # nopep8\n\n        django = django_module\n        django.setup()\n        from django.core.management import call_command as django_call_command  # nopep8\n        from acme_srv.models import (\n            Status as StatusModel,\n            Housekeeping as HousekeepingModel,\n        )  # nopep8\n        from acme_srv.version import __dbversion__ as db_version  # nopep8\n\n        call_command = django_call_command\n        Status = StatusModel\n        Housekeeping = HousekeepingModel\n        __dbversion__ = db_version\n\n        return True\n    except ImportError as e:\n        print(f\"Error importing Django modules: {e}\", file=sys.stderr)\n        return False\n    except Exception as e:\n        print(f\"Error during Django setup: {e}\", file=sys.stderr)\n        return False\n\n\ndef run_migrations():\n    \"\"\"Run Django migrations\"\"\"\n    try:\n        print(\"Running Django migrations...\")\n        call_command(\"makemigrations\", interactive=False)\n        print(\"Migrations created successfully.\")\n\n        call_command(\"migrate\", interactive=False)\n        print(\"Migrations applied successfully.\")\n        return True\n    except Exception as e:\n        print(f\"Error during Django operations: {e}\", file=sys.stderr)\n        return False\n\n\ndef update_status_fields():\n    \"\"\"Update status fields in the database\"\"\"\n    exit_code = 0\n    print(\"adding additional status fields to table...\")\n\n    for status in STATUS_LIST:\n        try:\n            _, _SCREATED = Status.objects.update_or_create(\n                name=status, defaults={\"name\": status}\n            )\n        except Exception as e:\n            print(f\"Error updating status '{status}': {e}\", file=sys.stderr)\n            exit_code = 1\n\n    return exit_code == 0\n\n\ndef update_db_version():\n    \"\"\"Update database version\"\"\"\n    try:\n        print(\"update dbversion to {0}...\".format(__dbversion__))\n        _, _HCREATED = Housekeeping.objects.update_or_create(\n            name=\"dbversion\", defaults={\"name\": \"dbversion\", \"value\": __dbversion__}\n        )\n        print(\"Database version updated successfully.\")\n        return True\n    except Exception as e:\n        print(f\"Error updating database version: {e}\", file=sys.stderr)\n        return False\n\n\ndef main():\n    \"\"\"Main function that orchestrates the database update process\"\"\"\n    if not setup_django():\n        return 1\n\n    exit_code = 0\n\n    if not run_migrations():\n        exit_code = 1\n\n    if not update_status_fields():\n        exit_code = 1\n\n    if not update_db_version():\n        exit_code = 1\n\n    if exit_code == 0:\n        print(\"Django database update completed successfully.\")\n    else:\n        print(\"Django database update completed with errors.\", file=sys.stderr)\n\n    return exit_code\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "tools/eab_chk.py",
    "content": "#!/usr/bin/python3\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\nimport os.path\nimport argparse\nfrom typing import Tuple, Dict\nimport yaml\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\nfrom acme_srv.db_handler import initialize  # nopep8\n\ninitialize()\nfrom acme_srv.helper import (\n    logger_setup,\n    load_config,\n    config_eab_profile_load,\n    eab_handler_load,\n    print_debug,\n)  # nopep8\n\n\ndef _eab_dic_print(logger, eab_dic: Dict[str, str], config_dic: Dict[str, str]) -> None:\n    \"\"\"print eab dic\"\"\"\n    logger.debug(\"eab_print_dic()\")\n\n    if config_dic[\"summary\"] or config_dic[\"verbose\"] or config_dic[\"veryverbose\"]:\n        _summary_print(logger, eab_dic)\n\n    if config_dic[\"verbose\"]:\n        for key, value in eab_dic.items():\n            if \"hmac\" in value:\n                print(f'{key}: {value[\"hmac\"]}')\n            else:\n                print(f\"{key}: {value}\")\n    elif config_dic[\"veryverbose\"]:\n        print(yaml.dump(eab_dic, default_flow_style=False, default_style=\"\"))\n\n\ndef _summary_print(logger, eab_dic: Dict[str, str]) -> None:\n    \"\"\"print summary of eab dic\"\"\"\n    logger.debug(\"summary_print()\")\n    print(f\"Summary: {len(eab_dic.keys())} entries in kid_file\")\n\n\ndef _filter_eab_dic(logger, eab_dic: Dict[str, str], keyid: str) -> Dict[str, str]:\n    \"\"\"filter eab dic\"\"\"\n    logger.debug(\"_filter_eab_dic(%s)\", keyid)\n    return {k: v for k, v in eab_dic.items() if k == keyid}\n\n\ndef arg_parse() -> Tuple[bool, Dict[str, Dict[str, str]]]:\n    \"\"\"simple argparser\"\"\"\n    parser = argparse.ArgumentParser(description=\"eab_chk.py - verify eab keyfile\")\n    parser.add_argument(\"-c\", \"--configfile\", help=\"configfile\", required=True)\n    parser.add_argument(\n        \"-d\", \"--debug\", help=\"debug mode\", action=\"store_true\", default=False\n    )\n    parser.add_argument(\n        \"-v\", \"--verbose\", help=\"verbose\", action=\"store_true\", default=False\n    )\n    parser.add_argument(\n        \"-vv\",\n        \"--veryverbose\",\n        help=\"show enrollment profile\",\n        action=\"store_true\",\n        default=False,\n    )\n    clist = parser.add_mutually_exclusive_group()\n    clist.add_argument(\"-k\", \"--keyid\", help=\"keyid to filter\", default=None)\n    clist.add_argument(\n        \"-s\", \"--summary\", help=\"summary\", default=False, action=\"store_true\"\n    )\n\n    args = parser.parse_args()\n\n    debug = args.debug\n    config_dic = {\n        \"debug\": args.debug,\n        \"verbose\": args.verbose,\n        \"veryverbose\": args.veryverbose,\n        \"keyid\": args.keyid,\n        \"summary\": args.summary,\n        \"configfile\": args.configfile,\n    }\n\n    if not config_dic[\"summary\"] and not config_dic[\"keyid\"]:\n        config_dic[\"summary\"] = True\n\n    return (debug, config_dic)\n\n\ndef eab_dic_load(logger, acme_srv_dic: Dict[str, Dict[str, str]]) -> Dict[str, str]:\n    \"\"\"load eabhandler\"\"\"\n    logger.debug(\"eab_dic_load()\")\n\n    eab_profiling, eab_module = config_eab_profile_load(logger, acme_srv_dic)\n    if not eab_profiling:\n        eab_handler_module = eab_handler_load(logger, acme_srv_dic)\n        eab_module = eab_handler_module.EABhandler\n\n    with eab_module(logger) as eab_handler:\n        eab_dic = eab_handler.key_file_load()\n\n    return eab_dic\n\n\nif __name__ == \"__main__\":\n\n    DEBUG, CONFIG_DIC = arg_parse()\n\n    # setup logging\n    LOGGER = logger_setup(DEBUG)\n\n    # load config\n    if os.path.exists(CONFIG_DIC[\"configfile\"]):\n        ACME_SRV_DIC = load_config(cfg_file=CONFIG_DIC[\"configfile\"])\n    else:\n        ACME_SRV_DIC = {}\n        error_text = f'Configfile {CONFIG_DIC[\"configfile\"]} not found.'\n        LOGGER.debug(error_text)\n        print_debug(True, error_text)\n\n    if \"EABhandler\" in ACME_SRV_DIC:\n        EAB_DIC = eab_dic_load(LOGGER, ACME_SRV_DIC)\n\n        if \"keyid\" in CONFIG_DIC and CONFIG_DIC[\"keyid\"]:\n            EAB_DIC = _filter_eab_dic(LOGGER, EAB_DIC, CONFIG_DIC[\"keyid\"])\n\n    else:\n        EAB_DIC = None\n        print_debug(True, \"No EABhandler section in configfile\")\n\n    if EAB_DIC:\n        _eab_dic_print(LOGGER, EAB_DIC, CONFIG_DIC)\n"
  },
  {
    "path": "tools/entrust_mgr.py",
    "content": "#!/usr/bin/python3\n# -*- coding: utf-8 -*-\n\"\"\"entrust manager\"\"\"\nfrom __future__ import print_function\nimport sys\nimport os\nimport argparse\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\n# pylint: disable=E0401, E0611, C0209, C0413\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom examples.ca_handler.entrust_ca_handler import CAhandler  # nopep8\n\n\ndef arg_parse():\n    \"\"\"simple argparser\"\"\"\n\n    parser = argparse.ArgumentParser(\n        description=\"enturst_mgr.py - a simple enturst certificate mananger\"\n    )\n    parser.add_argument(\n        \"-d\", \"--debug\", help=\"debug mode\", action=\"store_true\", default=False\n    )\n    parser.add_argument(\n        \"-p\",\n        \"--pagination\",\n        help=\"amout of certificates to be fetch with a single rest-call\",\n        default=200,\n    )\n    parser.add_argument(\n        \"-s\",\n        \"--sortby\",\n        help=\"sortby fieldname [trackigId, status, serialNumber, expiresAfter]\",\n        default=\"trackingId\",\n    )\n    clist = parser.add_mutually_exclusive_group()\n    clist.add_argument(\n        \"-a\",\n        \"--filteractive\",\n        help=\"filter output to active accounts\",\n        action=\"store_true\",\n        default=False,\n    )\n    clist.add_argument(\"-r\", \"--revoke\", help=\"revoke <transaction_id>\", default=None)\n\n    args = parser.parse_args()\n\n    debug = args.debug\n    config_dic = {\n        \"debug\": args.debug,\n        \"filteractive\": args.filteractive,\n        \"revoke\": args.revoke,\n        \"pagination\": int(args.pagination),\n        \"sortby\": args.sortby,\n    }\n    return (debug, config_dic)\n\n\nif __name__ == \"__main__\":\n\n    DEBUG, CONFIG_DIC = arg_parse()\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    with CAhandler(logger=LOGGER) as ca_handler:\n        result = ca_handler.credential_check()\n        if not result:\n\n            if CONFIG_DIC[\"revoke\"]:\n                print(\n                    \"Revoking certificate with transaction_id: \", CONFIG_DIC[\"revoke\"]\n                )\n                CODE, CONTENT = ca_handler.revoke_by_trackingid(CONFIG_DIC[\"revoke\"])\n                if CODE == 200:\n                    print(\"Revocation successful\")\n                else:\n                    print(f\"Revocation failed with error: {CONTENT}\")\n            else:\n                # get list of certificates\n                cert_list = ca_handler.certificates_get(limit=CONFIG_DIC[\"pagination\"])\n                for cert in sorted(cert_list, key=lambda k: k[CONFIG_DIC[\"sortby\"]]):\n                    if (\n                        CONFIG_DIC[\"filteractive\"] and cert[\"status\"] == \"ACTIVE\"\n                    ) or not CONFIG_DIC[\"filteractive\"]:\n                        print(cert)\n        else:\n            print(\"Credential check failed: \", result)\n            sys.exit(1)\n"
  },
  {
    "path": "tools/invalidator.py",
    "content": "#!/usr/bin/python\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\n\nsys.path.insert(0, \"..\")\nsys.path.insert(1, \".\")\nimport time  # nopep8\nfrom acme_srv.helper import logger_setup, uts_to_date_utc  # nopep8\nfrom acme_srv.housekeeping import Housekeeping  # nopep8\n\n\nif __name__ == \"__main__\":\n\n    DEBUG = True\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n    SUFFIX = uts_to_date_utc(int(time.time()), \"%Y-%m-%d-%H%M%S\")\n\n    with Housekeeping(DEBUG, LOGGER) as housekeeping:\n\n        # manual order invalidation\n        order_list = housekeeping.orders_invalidate(\n            report_format=\"csv\", report_name=f\"orders_invalidate_{SUFFIX}\"\n        )\n\n        # manual authorization invalidation\n        authorization_list = housekeeping.authorizations_invalidate(\n            report_format=\"csv\", report_name=f\"authorization_expire_{SUFFIX}\"\n        )\n\n        # update issue_uts and expire_uts in certificates table\n        housekeeping.certificate_dates_update()\n"
  },
  {
    "path": "tools/mswcce_connection_test.py",
    "content": "#!/usr/bin/python3\n# -*- coding: utf-8 -*-\n\"\"\"CA handler for Microsoft Windows Client Certificate Enrollment Protocol (MS-WCCE)\"\"\"\nfrom __future__ import print_function\nimport sys\nimport os\n\nsys.path.append(\n    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))\n)\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)\n    )\n)\n# pylint: disable=E0401, E0611, C0209, C0413\nfrom acme_srv.helper import logger_setup  # nopep8\nfrom examples.ca_handler.mswcce_ca_handler import CAhandler  # nopep8\n\nif __name__ == \"__main__\":\n\n    # initialize logger\n    LOGGER = logger_setup(True)\n\n    with CAhandler(True, LOGGER) as ca_handler:\n        request = ca_handler.request_create()\n"
  },
  {
    "path": "tools/report_generator.py",
    "content": "#!/usr/bin/python\n\"\"\"database updater\"\"\"\n# pylint: disable=E0401, C0413\nimport sys\n\nsys.path.insert(0, \"..\")\nsys.path.insert(1, \".\")\nimport time  # nopep8\nfrom acme_srv.helper import logger_setup, uts_to_date_utc  # nopep8\nfrom acme_srv.housekeeping import Housekeeping  # nopep8\n\n\nif __name__ == \"__main__\":\n\n    DEBUG = True\n\n    # initialize logger\n    LOGGER = logger_setup(DEBUG)\n\n    SUFFIX = uts_to_date_utc(int(time.time()), \"%Y-%m-%d-%H%M%S\")\n\n    # this is just for testing\n    # from shutil import copyfile\n    # copyfile('db.sqlite3.old', 'db.sqlite3')\n    # copyfile('acme_srv/acme_srv.db.old', 'acme_srv/acme_srv.db')\n\n    with Housekeeping(DEBUG, LOGGER) as housekeeping:\n\n        # certificate report in json format\n        cert_report = housekeeping.certreport_get(\n            report_name=f\"certificate_report_{SUFFIX}\", report_format=\"json\"\n        )\n        # certificate report in csv format\n        housekeeping.certreport_get(report_name=f\"certificate_report_{SUFFIX}\")\n\n        # account report in json format\n        account_report = housekeeping.accountreport_get(\n            report_name=f\"account_report_{SUFFIX}\", report_format=\"json\", nested=True\n        )\n        # account report in csv report_format\n        housekeeping.accountreport_get(report_name=f\"account_report_{SUFFIX}\")\n\n        # certifiate cleanup (no delete) dump in json\n        cleanup_report = housekeeping.certificates_cleanup(\n            report_format=\"json\", report_name=f\"certificate_cleanup_{SUFFIX}\"\n        )\n        # certifiate cleanup (including delete) dump in csv\n        # housekeeping.certificates_cleanup(report_format='csv', report_name='certificate_cleanup_{0}'.format(SUFFIX), purge=True)\n\n        # manual order invalidation\n        order_list = housekeeping.orders_invalidate(\n            report_format=\"csv\", report_name=f\"orders_invalidate_{SUFFIX}\"\n        )\n\n        # manual authorization invalidation\n        authorization_list = housekeeping.authorizations_invalidate(\n            report_format=\"csv\", report_name=f\"authorization_expire_{SUFFIX}\"\n        )\n"
  }
]